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
46pub const DEFAULT_DIRECTORY_MAX_ENTRIES: usize = 100_000;
48
49pub const DEFAULT_DIRECTORY_MAX_PATH_BYTES: usize = 16 * 1024 * 1024;
51
52pub const DEFAULT_WALK_MAX_DEPTH: usize = 128;
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct ReadDirOptions {
58 max_entries: usize,
59 max_path_bytes: usize,
60}
61
62impl ReadDirOptions {
63 pub const fn new() -> Self {
64 Self {
65 max_entries: DEFAULT_DIRECTORY_MAX_ENTRIES,
66 max_path_bytes: DEFAULT_DIRECTORY_MAX_PATH_BYTES,
67 }
68 }
69
70 pub const fn unlimited() -> Self {
72 Self {
73 max_entries: usize::MAX,
74 max_path_bytes: usize::MAX,
75 }
76 }
77
78 pub const fn max_entries(mut self, entries: usize) -> Self {
79 self.max_entries = entries;
80 self
81 }
82
83 pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
84 self.max_path_bytes = bytes;
85 self
86 }
87
88 pub const fn entry_limit(self) -> usize {
89 self.max_entries
90 }
91
92 pub const fn path_byte_limit(self) -> usize {
93 self.max_path_bytes
94 }
95}
96
97impl Default for ReadDirOptions {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub struct WalkOptions {
106 directory: ReadDirOptions,
107 max_depth: usize,
108 truncate_at_max_depth: bool,
109}
110
111impl WalkOptions {
112 pub const fn new() -> Self {
113 Self {
114 directory: ReadDirOptions::new(),
115 max_depth: DEFAULT_WALK_MAX_DEPTH,
116 truncate_at_max_depth: false,
117 }
118 }
119
120 pub const fn unlimited() -> Self {
122 Self {
123 directory: ReadDirOptions::unlimited(),
124 max_depth: usize::MAX,
125 truncate_at_max_depth: false,
126 }
127 }
128
129 pub const fn max_entries(mut self, entries: usize) -> Self {
130 self.directory = self.directory.max_entries(entries);
131 self
132 }
133
134 pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
135 self.directory = self.directory.max_path_bytes(bytes);
136 self
137 }
138
139 pub const fn max_depth(mut self, depth: usize) -> Self {
140 self.max_depth = depth;
141 self
142 }
143
144 pub const fn truncate_at_max_depth(mut self, truncate: bool) -> Self {
147 self.truncate_at_max_depth = truncate;
148 self
149 }
150
151 pub const fn entry_limit(self) -> usize {
152 self.directory.entry_limit()
153 }
154
155 pub const fn path_byte_limit(self) -> usize {
156 self.directory.path_byte_limit()
157 }
158
159 pub const fn depth_limit(self) -> usize {
160 self.max_depth
161 }
162
163 pub const fn truncates_at_depth_limit(self) -> bool {
164 self.truncate_at_max_depth
165 }
166}
167
168impl Default for WalkOptions {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
176pub struct ReadAllOptions {
177 max_total_bytes: usize,
178}
179
180impl ReadAllOptions {
181 pub const fn new() -> Self {
182 Self {
183 max_total_bytes: DEFAULT_READ_ALLV_MAX_TOTAL_BYTES,
184 }
185 }
186
187 pub const fn max_total_bytes(mut self, bytes: usize) -> Self {
189 self.max_total_bytes = bytes;
190 self
191 }
192
193 pub const fn total_byte_limit(self) -> usize {
194 self.max_total_bytes
195 }
196}
197
198impl Default for ReadAllOptions {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204fn contract_error(
205 operation: &str,
206 index: Option<usize>,
207 detail: impl std::fmt::Display,
208) -> VfError {
209 VfError::transport(
210 index,
211 format!("{operation} backend contract violation: {detail}"),
212 )
213}
214
215fn take_single_result<T>(operation: &str, mut results: Vec<T>) -> VfResult<T> {
216 if results.len() != 1 {
217 return Err(contract_error(
218 operation,
219 None,
220 format!("returned {} results for one request", results.len()),
221 ));
222 }
223 Ok(results.pop().expect("validated one result"))
224}
225
226pub(crate) fn validate_read_results(
227 operation: &str,
228 requests: &[ReadOp],
229 results: &[ReadResult],
230) -> VfResult<()> {
231 if results.len() != requests.len() {
232 return Err(contract_error(
233 operation,
234 None,
235 format!(
236 "returned {} results for {} requests",
237 results.len(),
238 requests.len()
239 ),
240 ));
241 }
242 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
243 if result.file != request.file {
244 return Err(contract_error(
245 operation,
246 Some(index),
247 "result file does not match request",
248 ));
249 }
250 if result.data.len() > request.length {
251 return Err(contract_error(
252 operation,
253 Some(index),
254 format!(
255 "returned {} bytes for a {}-byte read",
256 result.data.len(),
257 request.length
258 ),
259 ));
260 }
261 if let VfOffset::At(expected) = request.offset
262 && result.offset != expected
263 {
264 return Err(contract_error(
265 operation,
266 Some(index),
267 format!("result offset {} does not match {expected}", result.offset),
268 ));
269 }
270 if request.length != 0 && result.data.is_empty() && !result.eof {
271 return Err(contract_error(
272 operation,
273 Some(index),
274 "read made no progress without reporting EOF",
275 ));
276 }
277 }
278 Ok(())
279}
280
281pub(crate) fn validate_read_into_results(
282 operation: &str,
283 requests: &[ReadOp],
284 results: &[ReadIntoResult],
285) -> VfResult<()> {
286 if results.len() != requests.len() {
287 return Err(contract_error(
288 operation,
289 None,
290 format!(
291 "returned {} results for {} requests",
292 results.len(),
293 requests.len()
294 ),
295 ));
296 }
297 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
298 if result.file != request.file {
299 return Err(contract_error(
300 operation,
301 Some(index),
302 "result file does not match request",
303 ));
304 }
305 if result.read > request.length {
306 return Err(contract_error(
307 operation,
308 Some(index),
309 format!(
310 "returned {} bytes for a {}-byte read",
311 result.read, request.length
312 ),
313 ));
314 }
315 if let VfOffset::At(expected) = request.offset
316 && result.offset != expected
317 {
318 return Err(contract_error(
319 operation,
320 Some(index),
321 format!("result offset {} does not match {expected}", result.offset),
322 ));
323 }
324 if request.length != 0 && result.read == 0 && !result.eof {
325 return Err(contract_error(
326 operation,
327 Some(index),
328 "read made no progress without reporting EOF",
329 ));
330 }
331 }
332 Ok(())
333}
334
335pub(crate) fn validate_write_results(
336 operation: &str,
337 requests: &[WriteOpRef<'_>],
338 results: &[WriteResult],
339) -> VfResult<()> {
340 if results.len() != requests.len() {
341 return Err(contract_error(
342 operation,
343 None,
344 format!(
345 "returned {} results for {} requests",
346 results.len(),
347 requests.len()
348 ),
349 ));
350 }
351 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
352 if result.file != *request.file {
353 return Err(contract_error(
354 operation,
355 Some(index),
356 "result file does not match request",
357 ));
358 }
359 if result.written > request.data.len() {
360 return Err(contract_error(
361 operation,
362 Some(index),
363 format!(
364 "reported {} bytes written for a {}-byte write",
365 result.written,
366 request.data.len()
367 ),
368 ));
369 }
370 if let VfOffset::At(expected) = request.offset
371 && result.offset != expected
372 {
373 return Err(contract_error(
374 operation,
375 Some(index),
376 format!("result offset {} does not match {expected}", result.offset),
377 ));
378 }
379 }
380 Ok(())
381}
382
383pub trait VecFs {
398 #[deprecated(note = "use the NFS backend's NfsExtensions trait")]
400 fn nfs_minorversion(&self) -> Option<u32> {
401 None
402 }
403
404 #[deprecated(note = "use the SMB backend's SmbExtensions trait")]
407 fn smb_dialect(&self) -> Option<u16> {
408 None
409 }
410
411 fn capabilities(&self) -> u64 {
414 0
415 }
416
417 fn typed_capabilities(&self) -> Capabilities {
419 Capabilities::from_bits_retain(self.capabilities())
420 }
421
422 fn abs_path(&self, path: &Path) -> PathBuf;
433
434 fn open_by_path(
438 &mut self,
439 base: VfPathBase,
440 pathname: &Path,
441 flags: i32,
442 mode: u32,
443 ) -> VfResult<VfFile>;
444
445 fn close(&mut self, tcf: &VfFile) -> VfResult<()>;
447
448 fn sync_data(&mut self, tcf: &VfFile) -> VfResult<()>;
451
452 fn sync_all(&mut self, tcf: &VfFile) -> VfResult<()> {
454 self.sync_data(tcf)
455 }
456
457 fn chdir(&mut self, path: &Path) -> VfResult<()>;
459
460 fn getcwd(&self) -> PathBuf;
462
463 fn readv(&mut self, reads: &[ReadOp]) -> VfResult<Vec<ReadResult>>;
466
467 fn readv_into(
471 &mut self,
472 reads: &[ReadOp],
473 buffers: &mut [&mut [u8]],
474 ) -> VfResult<Vec<ReadIntoResult>> {
475 if reads.len() != buffers.len() {
476 return Err(VfError::client(0, ERR_INVAL));
477 }
478 for (index, (request, buffer)) in reads.iter().zip(buffers.iter()).enumerate() {
479 if request.length != buffer.len() {
480 return Err(VfError::client(index, ERR_INVAL));
481 }
482 }
483 let results = self.readv(reads)?;
484 validate_read_results("readv_into", reads, &results)?;
485 Ok(results
486 .into_iter()
487 .zip(buffers.iter_mut())
488 .map(|(result, buffer)| {
489 buffer[..result.data.len()].copy_from_slice(&result.data);
490 ReadIntoResult {
491 file: result.file,
492 offset: result.offset,
493 read: result.data.len(),
494 eof: result.eof,
495 }
496 })
497 .collect())
498 }
499
500 fn read_allv(&mut self, files: &[VfFile]) -> VfResult<Vec<Vec<u8>>> {
504 self.read_allv_with_options(files, ReadAllOptions::default())
505 }
506
507 fn read_allv_with_options(
509 &mut self,
510 files: &[VfFile],
511 options: ReadAllOptions,
512 ) -> VfResult<Vec<Vec<u8>>> {
513 let mut out: Vec<Vec<u8>> = files.iter().map(|_| Vec::new()).collect();
514 let mut total = 0usize;
515 let mut limit_error = None;
516 let stream_budget = options
517 .total_byte_limit()
518 .clamp(1, DEFAULT_READ_ALLV_MAX_TOTAL_BYTES);
519 let chunk_size = stream_budget.min(1024 * 1024);
520 self.read_streamv(
521 files,
522 chunk_size,
523 stream_budget,
524 &mut |index, _, data, _| {
525 let Some(next_total) = total.checked_add(data.len()) else {
526 limit_error = Some(index);
527 return false;
528 };
529 if next_total > options.total_byte_limit() {
530 limit_error = Some(index);
531 return false;
532 }
533 out[index].extend_from_slice(data);
534 total = next_total;
535 true
536 },
537 )?;
538 if let Some(index) = limit_error {
539 return Err(VfError::failure(index, libc::EFBIG as u32));
540 }
541 Ok(out)
542 }
543
544 fn writev(&mut self, writes: &[WriteOp]) -> VfResult<Vec<WriteResult>>;
547
548 fn writev_borrowed(&mut self, writes: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>> {
552 let owned: Vec<WriteOp> = writes
553 .iter()
554 .map(|write| WriteOp {
555 file: write.file.clone(),
556 offset: write.offset,
557 data: write.data.to_vec(),
558 creation: write.creation,
559 truncate: write.truncate,
560 })
561 .collect();
562 self.writev(&owned)
563 }
564
565 fn fseek(&mut self, tcf: &VfFile, offset: i64, whence: SeekFrom) -> VfResult<i64>;
571
572 fn getattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
575
576 fn lgetattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
579
580 fn setattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
586
587 fn lsetattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
592
593 fn listdir(
600 &mut self,
601 dir: &Path,
602 masks: AttrMask,
603 max_count: usize,
604 recursive: bool,
605 ) -> VfResult<Vec<VfAttrs>>;
606
607 fn walk(
615 &mut self,
616 root: &Path,
617 masks: AttrMask,
618 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
619 ) -> VfResult<Vec<WalkEntry>> {
620 self.walk_with_options(root, masks, WalkOptions::default(), sort)
621 }
622
623 fn walk_with_options(
625 &mut self,
626 root: &Path,
627 masks: AttrMask,
628 options: WalkOptions,
629 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
630 ) -> VfResult<Vec<WalkEntry>> {
631 let mut out = Vec::new();
635 let mut stack = vec![(root.to_path_buf(), 0usize)];
636 let mut entry_count = 0usize;
637 let mut path_bytes = 0usize;
638 while let Some((dir, depth)) = stack.pop() {
639 let remaining = options.entry_limit().saturating_sub(entry_count);
640 let request_count = remaining.saturating_add(1);
641 let mut entries = self.listdir(&dir, masks, request_count, false)?;
642 if entries.len() > remaining {
643 return Err(
644 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
645 );
646 }
647 for entry in &entries {
648 let bytes = entry.file.path().map_or(0, |path| path.as_os_str().len());
649 path_bytes = path_bytes.checked_add(bytes).ok_or_else(|| {
650 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
651 })?;
652 if path_bytes > options.path_byte_limit() {
653 return Err(VfError::failure(entry_count, libc::EFBIG as u32)
654 .with_context("walk", &dir));
655 }
656 entry_count += 1;
657 }
658 sort(dir.as_path(), &mut entries);
659 let subdirs: Vec<PathBuf> = entries
660 .iter()
661 .filter(|e| e.ftype == VfType::Directory)
662 .filter_map(|e| e.file.path().map(|p| p.to_path_buf()))
663 .collect();
664 if !subdirs.is_empty()
665 && depth >= options.depth_limit()
666 && !options.truncates_at_depth_limit()
667 {
668 return Err(
669 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
670 );
671 }
672 if depth < options.depth_limit() {
673 for s in subdirs.into_iter().rev() {
674 stack.push((s, depth + 1));
675 }
676 }
677 out.push(WalkEntry { path: dir, entries });
678 }
679 Ok(out)
680 }
681
682 fn renamev(&mut self, pairs: &[(VfFile, VfFile)]) -> VfRes;
684
685 fn removev(&mut self, files: &[VfFile]) -> VfRes;
687
688 fn mkdirv(&mut self, dirs: &[VfAttrs]) -> VfRes;
690
691 fn symlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
693
694 fn readlinkv(&mut self, paths: &[&Path]) -> VfResult<Vec<Vec<u8>>>;
696
697 fn hardlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
699
700 fn dupv(&mut self, pairs: &[ExtentPair]) -> VfRes;
703
704 fn lcopyv(&mut self, pairs: &[ExtentPair]) -> VfRes;
708
709 fn write_adb(&mut self, patterns: &[Adb]) -> VfResult<Vec<usize>>;
712
713 fn read_streamv(
722 &mut self,
723 files: &[VfFile],
724 chunk_size: usize,
725 memory_limit: usize,
726 cb: &mut ReadStreamCallback<'_>,
727 ) -> VfRes {
728 use std::collections::VecDeque;
729
730 if chunk_size == 0 || memory_limit == 0 {
731 return Err(VfError::failure(0, ERR_INVAL));
732 }
733 let mut pending: VecDeque<usize> = (0..files.len()).collect();
734 let mut offsets = vec![0u64; files.len()];
735 while !pending.is_empty() {
736 let mut budget = memory_limit;
737 let mut batch_indices = Vec::new();
738 let mut reads = Vec::new();
739 while budget > 0 && !pending.is_empty() {
740 let index = pending.pop_front().expect("pending was non-empty");
741 let length = chunk_size.min(budget);
742 reads.push(ReadOp::at(files[index].clone(), offsets[index], length));
743 batch_indices.push(index);
744 budget -= length;
745 }
746 let results = self.readv(&reads).map_err(|error| {
747 error.index_opt().map_or(error.clone(), |local_index| {
748 batch_indices
749 .get(local_index)
750 .copied()
751 .map_or(error.clone(), |index| error.with_index(index))
752 })
753 })?;
754 validate_read_results("read_streamv", &reads, &results).map_err(|error| {
755 error.index_opt().map_or(error.clone(), |local_index| {
756 batch_indices
757 .get(local_index)
758 .copied()
759 .map_or(error.clone(), |index| error.with_index(index))
760 })
761 })?;
762 for (batch_index, result) in results.into_iter().enumerate() {
763 let index = batch_indices[batch_index];
764 let offset = offsets[index];
765 let eof = result.eof;
766 offsets[index] = offset
767 .checked_add(result.data.len() as u64)
768 .ok_or_else(|| VfError::failure(index, libc::EOVERFLOW as u32))?;
769 if !cb(index, offset, &result.data, eof) {
770 return Ok(());
771 }
772 if !eof {
773 pending.push_back(index);
774 }
775 }
776 }
777 Ok(())
778 }
779
780 fn rm(&mut self, objs: &[&Path], recursive: bool) -> VfRes {
797 self.rm_with_options(objs, recursive, RemoveOptions::default())
798 }
799
800 fn rm_with_options(
809 &mut self,
810 objs: &[&Path],
811 recursive: bool,
812 options: RemoveOptions,
813 ) -> VfRes {
814 if options != RemoveOptions::default() {
815 return Err(VfError::unsupported(0));
816 }
817 if objs.is_empty() {
818 return Ok(());
819 }
820 if !recursive {
821 let files: Vec<VfFile> = objs.iter().map(|p| VfFile::from_os_path(p)).collect();
822 return self.removev(&files);
823 }
824
825 for (root_index, root) in objs.iter().enumerate() {
826 self.before_remove_type(root_index)?;
827 let mut root_attrs = VfAttrs {
829 file: VfFile::from_os_path(root),
830 masks: AttrMask::default(),
831 ..VfAttrs::default()
832 };
833 self.lgetattrsv(std::slice::from_mut(&mut root_attrs))
834 .map_err(|error| error.map_index(|_| root_index))?;
835 if root_attrs.ftype != VfType::Directory {
836 self.removev(&[VfFile::from_os_path(root)])
837 .map_err(|error| error.map_index(|_| root_index))?;
838 continue;
839 }
840
841 let mut dir_levels: Vec<Vec<PathBuf>> = Vec::new();
844 let mut frontier: Vec<PathBuf> = vec![root.to_path_buf()];
845 while !frontier.is_empty() {
846 let refs: Vec<&Path> = frontier.iter().map(PathBuf::as_path).collect();
847 let mut per_dir: Vec<Vec<VfAttrs>> = (0..refs.len()).map(|_| Vec::new()).collect();
848 {
849 let index_of: std::collections::HashMap<&Path, usize> =
850 refs.iter().enumerate().map(|(i, p)| (*p, i)).collect();
851 let mut cb = |attrs: &VfAttrs, dir: &Path| {
852 if let Some(&slot) = index_of.get(dir) {
853 per_dir[slot].push(attrs.clone());
854 }
855 true
856 };
857 self.listdirv(&refs, AttrMask::default(), 0, false, &mut cb)
858 .map_err(|error| error.map_index(|_| root_index))?;
859 }
860
861 let mut next: Vec<PathBuf> = Vec::new();
862 for entries in per_dir {
863 let mut files: Vec<VfFile> = Vec::new();
864 for attrs in entries {
865 if attrs.ftype == VfType::Directory {
866 if let Some(path) = attrs.file.path() {
867 next.push(path.to_path_buf());
868 }
869 } else {
870 files.push(attrs.file);
871 }
872 }
873 if !files.is_empty() {
874 self.removev(&files)
875 .map_err(|error| error.map_index(|_| root_index))?;
876 }
877 }
878 dir_levels.push(frontier);
879 frontier = next;
880 }
881
882 for level in dir_levels.iter().rev() {
883 let dirs: Vec<VfFile> = level
884 .iter()
885 .map(|path| VfFile::from_os_path(path))
886 .collect();
887 self.removev(&dirs)
888 .map_err(|error| error.map_index(|_| root_index))?;
889 }
890 }
891 Ok(())
892 }
893
894 fn open_dir(&mut self, path: &Path) -> VfResult<VfDir> {
906 let mut attrs = VfAttrs {
907 file: VfFile::from_os_path(path),
908 masks: AttrMask::default(),
909 ..VfAttrs::default()
910 };
911 self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
912 if attrs.ftype != VfType::Directory {
913 return Err(VfError::failure(0, ERR_NOTDIR));
914 }
915 Ok(VfDir::Path(path.to_path_buf()))
916 }
917
918 fn rm_dir_contents(&mut self, dir: &VfDir) -> VfRes {
922 self.rm_dir_contents_with_options(dir, RemoveOptions::default())
923 }
924
925 fn rm_dir_contents_with_options(&mut self, dir: &VfDir, options: RemoveOptions) -> VfRes {
927 match dir {
928 VfDir::Path(path) => self.rm_contents_with_options(path, options),
929 _ => Err(VfError::unsupported(0)),
930 }
931 }
932
933 fn close_dir(&mut self, _dir: &VfDir) -> VfResult<()> {
935 Ok(())
936 }
937
938 fn rm_contents(&mut self, dir: &Path) -> VfRes {
944 self.rm_contents_with_options(dir, RemoveOptions::default())
945 }
946
947 fn rm_contents_with_options(&mut self, dir: &Path, options: RemoveOptions) -> VfRes {
949 if options != RemoveOptions::default() {
950 return Err(VfError::unsupported(0));
951 }
952 let entries = self
953 .listdir(dir, AttrMask::default(), 0, false)
954 .map_err(|error| error.with_index(0))?;
955 let mut files: Vec<VfFile> = Vec::new();
956 let mut dirs: Vec<PathBuf> = Vec::new();
957 for attrs in entries {
958 if attrs.ftype == VfType::Directory {
959 if let Some(path) = attrs.file.path() {
960 dirs.push(path.to_path_buf());
961 }
962 } else {
963 files.push(attrs.file);
964 }
965 }
966 if !files.is_empty() {
967 self.removev(&files).map_err(|error| error.with_index(0))?;
968 }
969 for sub in dirs {
970 self.rm(&[sub.as_path()], true)
971 .map_err(|error| error.with_index(0))?;
972 }
973 Ok(())
974 }
975
976 fn ensure_empty_dir(&mut self, dir: &Path) -> VfRes {
982 match self.mkdir(dir, 0o777) {
983 Ok(()) => Ok(()),
984 Err(error) if error.err_no() == ERR_EXIST => {
985 let mut attrs = VfAttrs {
986 file: VfFile::from_os_path(dir),
987 masks: AttrMask::default(),
988 ..VfAttrs::default()
989 };
990 self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
991 if attrs.ftype != VfType::Directory {
992 return Err(VfError::failure(0, ERR_NOTDIR));
993 }
994 self.rm_contents(dir)
995 }
996 Err(error) => Err(error),
997 }
998 }
999
1000 fn cp_recursive(
1002 &mut self,
1003 src_dir: &Path,
1004 dst: &Path,
1005 symlinks: bool,
1006 use_server_side_copy: bool,
1007 ) -> VfRes;
1008
1009 fn vf_path(&self, file: &VfFile) -> VfResult<PathBuf> {
1018 match file {
1019 VfFile::Path {
1020 base: VfPathBase::Abs,
1021 path,
1022 } => Ok(self.abs_path(&Path::new("/").join(path))),
1023 VfFile::Path {
1024 base: VfPathBase::Cwd,
1025 path,
1026 }
1027 | VfFile::CwdPath(path) => Ok(self.abs_path(path)),
1028 VfFile::Cwd => Ok(self.abs_path(Path::new(""))),
1029 VfFile::Descriptor(_) | VfFile::Saved => Err(VfError::failure(0, ERR_INVAL)),
1030 _ => Err(VfError::failure(0, ERR_INVAL)),
1031 }
1032 }
1033
1034 fn open(&mut self, pathname: &Path, flags: i32, mode: u32) -> VfResult<VfFile> {
1036 self.open_by_path(VfPathBase::Cwd, pathname, flags, mode)
1037 }
1038
1039 fn read(&mut self, file: &VfFile, offset: u64, length: usize) -> VfResult<Vec<u8>> {
1041 let requests = [ReadOp::at(file.clone(), offset, length)];
1042 let mut results = self.readv(&requests)?;
1043 validate_read_results("read", &requests, &results)?;
1044 Ok(results.pop().expect("validated one result").data)
1045 }
1046
1047 fn write(&mut self, file: &VfFile, offset: u64, data: &[u8]) -> VfResult<usize> {
1049 let owned = WriteOp::at(file.clone(), offset, data.to_vec());
1050 let requests = [WriteOpRef {
1051 file: &owned.file,
1052 offset: owned.offset,
1053 data: &owned.data,
1054 creation: owned.creation,
1055 truncate: owned.truncate,
1056 }];
1057 let mut results = self.writev(std::slice::from_ref(&owned))?;
1058 validate_write_results("write", &requests, &results)?;
1059 Ok(results.pop().expect("validated one result").written)
1060 }
1061
1062 #[doc(hidden)]
1068 fn open_many(
1069 &mut self,
1070 paths: &[&Path],
1071 flags: &[i32],
1072 modes: &[u32],
1073 ) -> VfResult<ManyResults<VfFile>> {
1074 if paths.len() != flags.len() || paths.len() != modes.len() {
1075 return Err(VfError::failure(0, ERR_INVAL));
1076 }
1077 let mut results = Vec::with_capacity(paths.len());
1078 for ((path, flag), mode) in paths.iter().zip(flags).zip(modes) {
1079 match self.open(path, *flag, *mode) {
1080 Ok(file) => results.push(Ok(file)),
1081 Err(error) => {
1082 results.push(Err(error));
1083 break;
1084 }
1085 }
1086 }
1087 Ok(ManyResults::new(paths.len(), results))
1088 }
1089
1090 #[doc(hidden)]
1093 fn before_open_cleanup(&mut self, _index: usize, _file: &VfFile) -> VfResult<()> {
1094 Ok(())
1095 }
1096
1097 #[doc(hidden)]
1099 fn before_remove_type(&mut self, _index: usize) -> VfResult<()> {
1100 Ok(())
1101 }
1102
1103 fn openv(&mut self, paths: &[&Path], flags: &[i32], modes: &[u32]) -> VfResult<Vec<VfFile>> {
1107 let results = self.open_many(paths, flags, modes)?;
1108 results.try_collect_with_cleanup(paths.len(), |index, file| {
1109 let injected = self.before_open_cleanup(index, file);
1110 let closed = self.close(file);
1111 injected.and(closed)
1112 })
1113 }
1114
1115 fn openv_simple(&mut self, paths: &[&Path], flags: i32, mode: u32) -> VfResult<Vec<VfFile>> {
1118 let flags_v = vec![flags; paths.len()];
1119 let modes_v = vec![mode; paths.len()];
1120 self.openv(paths, &flags_v, &modes_v)
1121 }
1122
1123 fn closev(&mut self, files: &[VfFile]) -> VfRes {
1125 for (i, f) in files.iter().enumerate() {
1126 self.close(f).map_err(|e| e.with_index(i))?;
1127 }
1128 Ok(())
1129 }
1130
1131 fn stat(&mut self, path: &Path) -> VfResult<VfAttrs> {
1133 let mut a = VfAttrs {
1134 file: VfFile::from_os_path(path),
1135 masks: AttrMask::stat(),
1136 ..VfAttrs::default()
1137 };
1138 self.getattrsv(std::slice::from_mut(&mut a))?;
1139 Ok(a)
1140 }
1141
1142 fn lstat(&mut self, path: &Path) -> VfResult<VfAttrs> {
1144 let mut a = VfAttrs {
1145 file: VfFile::from_os_path(path),
1146 masks: AttrMask::stat(),
1147 ..VfAttrs::default()
1148 };
1149 self.lgetattrsv(std::slice::from_mut(&mut a))?;
1150 Ok(a)
1151 }
1152
1153 fn fstat(&mut self, tcf: &VfFile) -> VfResult<VfAttrs> {
1155 let mut a = VfAttrs {
1156 file: tcf.clone(),
1157 masks: AttrMask::stat(),
1158 ..VfAttrs::default()
1159 };
1160 self.getattrsv(std::slice::from_mut(&mut a))?;
1161 Ok(a)
1162 }
1163
1164 fn exists(&mut self, path: &Path) -> VfResult<bool> {
1168 match self.lstat(path) {
1169 Ok(_) => Ok(true),
1170 Err(e) if e.err_no() == ERR_NOENT => Ok(false),
1171 Err(e) => Err(e),
1172 }
1173 }
1174
1175 fn file_type(&mut self, path: &Path) -> VfResult<VfType> {
1178 Ok(self.lstat(path)?.ftype)
1179 }
1180
1181 fn listdirv(
1184 &mut self,
1185 dirs: &[&Path],
1186 masks: AttrMask,
1187 max_entries: usize,
1188 recursive: bool,
1189 cb: &mut dyn FnMut(&VfAttrs, &Path) -> bool,
1190 ) -> VfRes {
1191 for (i, d) in dirs.iter().enumerate() {
1192 let entries = self
1193 .listdir(d, masks, max_entries, recursive)
1194 .map_err(|e| e.with_index(i))?;
1195 for e in &entries {
1196 let entry_dir = if recursive {
1200 e.file.path().and_then(Path::parent).unwrap_or(d)
1201 } else {
1202 d
1203 };
1204 if !cb(e, entry_dir) {
1205 return Ok(());
1206 }
1207 }
1208 }
1209 Ok(())
1210 }
1211
1212 fn unlink(&mut self, pathname: &Path) -> VfResult<()> {
1214 self.removev(&[VfFile::from_os_path(pathname)])
1215 }
1216
1217 fn unlinkv(&mut self, pathnames: &[&Path]) -> VfRes {
1219 let files: Vec<VfFile> = pathnames.iter().map(|p| VfFile::from_os_path(p)).collect();
1220 self.removev(&files)
1221 }
1222
1223 fn mkdir(&mut self, path: &Path, mode: u32) -> VfResult<()> {
1225 let a = VfAttrs {
1226 file: VfFile::from_os_path(path),
1227 masks: AttrMask::MODE,
1228 mode,
1229 ..VfAttrs::default()
1230 };
1231 self.mkdirv(std::slice::from_ref(&a))
1232 }
1233
1234 fn symlink(&mut self, oldpath: &Path, newpath: &Path) -> VfResult<()> {
1236 self.symlinkv(
1237 std::slice::from_ref(&oldpath),
1238 std::slice::from_ref(&newpath),
1239 )
1240 }
1241
1242 fn readlink(&mut self, path: &Path) -> VfResult<Vec<u8>> {
1244 take_single_result("readlink", self.readlinkv(std::slice::from_ref(&path))?)
1245 }
1246
1247 fn ldupv(&mut self, pairs: &[ExtentPair]) -> VfRes {
1251 self.dupv(pairs)
1252 }
1253
1254 fn copyv(&mut self, pairs: &[ExtentPair]) -> VfRes {
1259 self.dupv(pairs)
1260 }
1261
1262 fn ensure_dir(&mut self, dir: &Path, mode: u32) -> VfResult<()> {
1268 use std::path::Component;
1269 let mut so_far = PathBuf::new();
1270 for comp in self.abs_path(dir).components() {
1271 if let Component::Normal(part) = comp {
1272 so_far.push(part);
1273 let full = Path::new("/").join(&so_far);
1274 match self.mkdir(&full, mode) {
1275 Ok(()) => {}
1276 Err(e) if e.err_no() == ERR_EXIST => {}
1277 Err(e) => return Err(e),
1278 }
1279 }
1280 }
1281 Ok(())
1282 }
1283}
1284
1285pub trait VecFsExt: VecFs {
1292 fn stat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
1293 self.stat(path.as_ref())
1294 }
1295
1296 fn lstat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
1297 self.lstat(path.as_ref())
1298 }
1299
1300 fn exists_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<bool> {
1301 self.exists(path.as_ref())
1302 }
1303
1304 fn file_type_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfType> {
1305 self.file_type(path.as_ref())
1306 }
1307
1308 fn open_path<P: AsRef<Path>>(&mut self, path: P, flags: i32, mode: u32) -> VfResult<VfFile> {
1309 self.open(path.as_ref(), flags, mode)
1310 }
1311
1312 fn mkdir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
1313 self.mkdir(path.as_ref(), mode)
1314 }
1315
1316 fn ensure_dir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
1317 self.ensure_dir(path.as_ref(), mode)
1318 }
1319
1320 fn chdir_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
1321 self.chdir(path.as_ref())
1322 }
1323
1324 fn unlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
1325 self.unlink(path.as_ref())
1326 }
1327
1328 fn readlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<Vec<u8>> {
1329 self.readlink(path.as_ref())
1330 }
1331
1332 fn symlink_path<P, Q>(&mut self, oldpath: P, newpath: Q) -> VfResult<()>
1333 where
1334 P: AsRef<Path>,
1335 Q: AsRef<Path>,
1336 {
1337 self.symlink(oldpath.as_ref(), newpath.as_ref())
1338 }
1339
1340 fn listdir_path<P: AsRef<Path>>(
1341 &mut self,
1342 path: P,
1343 masks: AttrMask,
1344 max_count: usize,
1345 recursive: bool,
1346 ) -> VfResult<Vec<VfAttrs>> {
1347 self.listdir(path.as_ref(), masks, max_count, recursive)
1348 }
1349
1350 fn walk_path<P: AsRef<Path>>(
1351 &mut self,
1352 root: P,
1353 masks: AttrMask,
1354 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
1355 ) -> VfResult<Vec<WalkEntry>> {
1356 self.walk(root.as_ref(), masks, sort)
1357 }
1358
1359 fn openv_paths<P: AsRef<Path>>(
1360 &mut self,
1361 paths: &[P],
1362 flags: &[i32],
1363 modes: &[u32],
1364 ) -> VfResult<Vec<VfFile>> {
1365 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1366 self.openv(&refs, flags, modes)
1367 }
1368
1369 fn openv_simple_paths<P: AsRef<Path>>(
1370 &mut self,
1371 paths: &[P],
1372 flags: i32,
1373 mode: u32,
1374 ) -> VfResult<Vec<VfFile>> {
1375 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1376 self.openv_simple(&refs, flags, mode)
1377 }
1378
1379 fn unlinkv_paths<P: AsRef<Path>>(&mut self, paths: &[P]) -> VfRes {
1380 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1381 self.unlinkv(&refs)
1382 }
1383
1384 fn rm_paths<P: AsRef<Path>>(&mut self, paths: &[P], recursive: bool) -> VfRes {
1385 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1386 self.rm(&refs, recursive)
1387 }
1388
1389 fn rm_recursive_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1390 self.rm(&[path.as_ref()], true)
1391 }
1392
1393 fn rm_contents_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1394 self.rm_contents(path.as_ref())
1395 }
1396
1397 fn ensure_empty_dir_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1398 self.ensure_empty_dir(path.as_ref())
1399 }
1400}
1401
1402impl<T: VecFs + ?Sized> VecFsExt for T {}
1403
1404pub fn rm_recursive(fs: &mut impl VecFs, dir: &Path) -> VfRes {
1406 fs.rm(&[dir], true)
1407}
1408
1409#[cfg(test)]
1410mod contract_tests {
1411 use super::*;
1412
1413 fn request() -> ReadOp {
1414 ReadOp::at(VfFile::from_path("/file"), 0, 1)
1415 }
1416
1417 fn result() -> ReadResult {
1418 ReadResult {
1419 file: VfFile::from_path("/file"),
1420 offset: 0,
1421 data: vec![1],
1422 eof: true,
1423 }
1424 }
1425
1426 #[test]
1427 fn read_result_cardinality_is_checked_before_indexing() {
1428 let requests = [request(), request()];
1429 assert!(validate_read_results("test", &requests, &[result()]).is_err());
1430 assert!(validate_read_results("test", &requests[..1], &[result(), result()]).is_err());
1431 }
1432
1433 #[test]
1434 fn read_result_identity_offset_progress_and_size_are_checked() {
1435 let request = request();
1436 for malformed in [
1437 ReadResult {
1438 file: VfFile::from_path("/other"),
1439 ..result()
1440 },
1441 ReadResult {
1442 offset: 1,
1443 ..result()
1444 },
1445 ReadResult {
1446 data: vec![1, 2],
1447 ..result()
1448 },
1449 ReadResult {
1450 data: Vec::new(),
1451 eof: false,
1452 ..result()
1453 },
1454 ] {
1455 assert!(
1456 validate_read_results("test", std::slice::from_ref(&request), &[malformed])
1457 .is_err()
1458 );
1459 }
1460 }
1461
1462 #[test]
1463 fn write_result_identity_offset_size_and_cardinality_are_checked() {
1464 let file = VfFile::from_path("/file");
1465 let request = WriteOpRef::new(&file, VfOffset::At(0), b"x");
1466 let valid = WriteResult {
1467 file: file.clone(),
1468 offset: 0,
1469 written: 1,
1470 stable: true,
1471 };
1472 assert!(validate_write_results("test", &[request], &[]).is_err());
1473 for malformed in [
1474 WriteResult {
1475 file: VfFile::from_path("/other"),
1476 ..valid.clone()
1477 },
1478 WriteResult {
1479 offset: 1,
1480 ..valid.clone()
1481 },
1482 WriteResult {
1483 written: 2,
1484 ..valid.clone()
1485 },
1486 ] {
1487 assert!(validate_write_results("test", &[request], &[malformed]).is_err());
1488 }
1489 }
1490
1491 #[test]
1492 fn scalar_result_cardinality_is_checked_without_panicking() {
1493 assert!(take_single_result::<u8>("readlink", Vec::new()).is_err());
1494 assert!(take_single_result("readlink", vec![1u8, 2]).is_err());
1495 assert_eq!(take_single_result("readlink", vec![7u8]).unwrap(), 7);
1496 }
1497}