1use std::collections::{HashMap, HashSet};
4use std::fmt;
5use std::io::{self, Read, Seek, SeekFrom as IoSeekFrom, Write};
6use std::path::{Component, Path, PathBuf};
7use std::sync::{Arc, Mutex};
8use std::time::SystemTime;
9
10use crate::traits::{validate_read_into_results, validate_read_results, validate_write_results};
11use crate::{
12 AttrMask, Capabilities, CopyFileSystem, DEFAULT_READ_MAX_BYTES, DirEntry, DirectoryFileSystem,
13 FileSystem, LinkFileSystem, Metadata, MetadataFileSystem, MetadataQuery, MetadataUpdate,
14 NamespaceFileSystem, OpenFlags, OpenRequest, Permissions, ReadAllOptions, ReadDirOptions,
15 ReadOp, ReadResult, ReadStreamOptions, RemoveOptions, SetAttributes, VecFs, VectorFileSystem,
16 VfDir, VfError, VfFile, VfOffset, VfResult, WriteOpRef, WriteResult,
17};
18
19pub const DEFAULT_READV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct FsReadResult {
25 pub offset: u64,
26 pub data: Vec<u8>,
27 pub eof: bool,
28}
29
30fn read_result(result: ReadResult) -> FsReadResult {
31 FsReadResult {
32 offset: result.offset,
33 data: result.data,
34 eof: result.eof,
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct FsReadIntoResult {
41 pub offset: u64,
42 pub read: usize,
43 pub eof: bool,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct FsWriteResult {
49 pub offset: u64,
50 pub written: usize,
51 pub stable: bool,
52}
53
54fn write_result(result: WriteResult) -> FsWriteResult {
55 FsWriteResult {
56 offset: result.offset,
57 written: result.written,
58 stable: result.stable,
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum StreamCompletion {
65 Complete,
66 Stopped { next_offset: u64 },
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum TraversalCompletion {
72 Complete,
73 Stopped,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct ResourceLimits {
80 pub max_read_bytes: usize,
84 pub stream_chunk_bytes: usize,
86 pub max_directory_entries: usize,
88 pub max_directory_path_bytes: usize,
90 pub max_walk_depth: usize,
92}
93
94impl Default for ResourceLimits {
95 fn default() -> Self {
96 Self {
97 max_read_bytes: DEFAULT_READ_MAX_BYTES,
98 stream_chunk_bytes: crate::DEFAULT_READ_STREAM_CHUNK_BYTES,
99 max_directory_entries: crate::DEFAULT_DIRECTORY_MAX_ENTRIES,
100 max_directory_path_bytes: crate::DEFAULT_DIRECTORY_MAX_PATH_BYTES,
101 max_walk_depth: crate::DEFAULT_WALK_MAX_DEPTH,
102 }
103 }
104}
105
106impl ResourceLimits {
107 pub fn directory_options(self) -> ReadDirOptions {
108 ReadDirOptions::new()
109 .max_entries(self.max_directory_entries)
110 .max_path_bytes(self.max_directory_path_bytes)
111 }
112
113 pub fn walk_options(self) -> crate::WalkOptions {
114 crate::WalkOptions::new()
115 .max_entries(self.max_directory_entries)
116 .max_path_bytes(self.max_directory_path_bytes)
117 .max_depth(self.max_walk_depth)
118 }
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
123pub struct DirectoryListing {
124 pub path: PathBuf,
125 pub entries: Vec<DirEntry>,
126}
127
128fn io_error(error: VfError) -> io::Error {
129 error.into()
130}
131
132fn poisoned() -> VfError {
133 VfError::client(0, crate::ERR_IO)
134}
135
136fn wrong_result_count(operation: &str, expected: usize, actual: usize) -> VfError {
137 VfError::transport_with_kind(
138 None,
139 crate::TransportKind::InvalidReply,
140 format!("{operation} backend returned {actual} results for {expected} requests"),
141 )
142}
143
144pub struct FsClient<F> {
148 inner: Arc<Mutex<F>>,
149 limits: ResourceLimits,
150}
151
152impl<F> Clone for FsClient<F> {
153 fn clone(&self) -> Self {
154 Self {
155 inner: Arc::clone(&self.inner),
156 limits: self.limits,
157 }
158 }
159}
160
161impl<F> fmt::Debug for FsClient<F> {
162 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163 formatter.debug_struct("FsClient").finish_non_exhaustive()
164 }
165}
166
167impl<F> FsClient<F> {
168 pub fn new(filesystem: F) -> Self {
169 Self {
170 inner: Arc::new(Mutex::new(filesystem)),
171 limits: ResourceLimits::default(),
172 }
173 }
174
175 pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
178 self.limits = limits;
179 self
180 }
181
182 pub fn limits(&self) -> ResourceLimits {
183 self.limits
184 }
185
186 fn lock(&self) -> VfResult<std::sync::MutexGuard<'_, F>> {
187 self.inner.lock().map_err(|_| poisoned())
188 }
189
190 pub fn into_inner(self) -> Result<F, Self> {
191 match Arc::try_unwrap(self.inner) {
192 Ok(mutex) => match mutex.into_inner() {
193 Ok(filesystem) => Ok(filesystem),
194 Err(poisoned) => Ok(poisoned.into_inner()),
195 },
196 Err(inner) => Err(Self {
197 inner,
198 limits: self.limits,
199 }),
200 }
201 }
202}
203
204impl<F: FileSystem> FsClient<F> {
205 pub fn capabilities(&self) -> VfResult<Capabilities> {
206 Ok(self.lock()?.capabilities())
207 }
208 pub fn open(&self, path: impl AsRef<Path>) -> VfResult<FsFile<F>> {
210 self.open_with(OpenRequest::new(path.as_ref(), OpenFlags::READ))
211 }
212
213 pub fn open_with(&self, request: OpenRequest) -> VfResult<FsFile<F>> {
214 let file = self.lock()?.open_one(&request)?;
215 Ok(FsFile {
216 inner: Arc::clone(&self.inner),
217 file: Some(file),
218 path: request.path,
219 })
220 }
221
222 pub fn open_options(&self) -> OpenOptions<'_, F> {
223 OpenOptions::new(self)
224 }
225
226 pub fn create(&self, path: impl AsRef<Path>) -> VfResult<FsFile<F>> {
227 self.open_with(OpenRequest::new(
228 path.as_ref(),
229 OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::TRUNCATE,
230 ))
231 }
232
233 pub fn read(&self, path: impl AsRef<Path>) -> VfResult<Vec<u8>> {
234 self.read_with_limit(path, self.limits.max_read_bytes)
235 }
236
237 pub fn read_with_limit(&self, path: impl AsRef<Path>, max_bytes: usize) -> VfResult<Vec<u8>> {
242 let path = path.as_ref();
243 let file = self.open(path)?;
244 let operation = self
245 .lock()?
246 .read_file(file.raw()?, max_bytes)
247 .map_err(|error| error.with_context("read", path))
248 .and_then(|data| {
249 if data.len() > max_bytes {
250 Err(VfError::client(0, libc::EFBIG as u32).with_context("read", path))
251 } else {
252 Ok(data)
253 }
254 });
255 let cleanup = file.close();
258 operation.and_then(|data| cleanup.map(|()| data))
259 }
260
261 pub fn read_to_string(&self, path: impl AsRef<Path>) -> VfResult<String> {
262 self.read_to_string_with_limit(path, self.limits.max_read_bytes)
263 }
264
265 pub fn read_to_string_with_limit(
267 &self,
268 path: impl AsRef<Path>,
269 max_bytes: usize,
270 ) -> VfResult<String> {
271 let path = path.as_ref();
272 String::from_utf8(self.read_with_limit(path, max_bytes)?)
273 .map_err(|_| VfError::client(0, crate::ERR_INVAL).with_context("read_to_string", path))
274 }
275
276 pub fn write(&self, path: impl AsRef<Path>, data: &[u8]) -> VfResult<()> {
277 let path = path.as_ref();
278 let mut file = self.create(path)?;
279 let mut written = 0;
280 while written < data.len() {
281 let count = file.write_native(&data[written..])?;
282 if count == 0 {
283 return Err(VfError::client(0, crate::ERR_IO).with_context("write", path));
284 }
285 written += count;
286 }
287 file.close()
288 }
289}
290
291impl<F: FileSystem> FsClient<F> {
292 pub fn read_stream(
300 &self,
301 path: impl AsRef<Path>,
302 callback: impl FnMut(u64, &[u8]) -> VfResult<bool>,
303 ) -> VfResult<StreamCompletion> {
304 self.read_stream_with_options(
305 path,
306 ReadStreamOptions::new().chunk_size(self.limits.stream_chunk_bytes),
307 callback,
308 )
309 }
310
311 pub fn read_stream_with_options(
313 &self,
314 path: impl AsRef<Path>,
315 options: ReadStreamOptions,
316 mut callback: impl FnMut(u64, &[u8]) -> VfResult<bool>,
317 ) -> VfResult<StreamCompletion> {
318 let chunk_size = options.chunk_size_bytes();
319 if chunk_size == 0 {
320 return Err(VfError::failure(0, crate::ERR_INVAL));
321 }
322
323 let file = self.open(path)?;
324 let raw_file = file.raw()?.clone();
325 let operation = (|| -> VfResult<StreamCompletion> {
326 let mut offset = 0u64;
327 loop {
328 let request = ReadOp::at(raw_file.clone(), offset, chunk_size);
329 let result = {
330 let mut backend = self.lock()?;
331 let result = backend.read_one(&request)?;
332 validate_read_results(
333 "read_stream",
334 std::slice::from_ref(&request),
335 std::slice::from_ref(&result),
336 )?;
337 result
338 };
339 let length = result.data.len();
340 if !result.data.is_empty() && !callback(offset, &result.data)? {
341 return Ok(StreamCompletion::Stopped {
342 next_offset: offset
343 .checked_add(length as u64)
344 .ok_or_else(|| VfError::client(0, crate::ERR_INVAL))?,
345 });
346 }
347 if result.eof {
348 return Ok(StreamCompletion::Complete);
349 }
350 offset = offset
351 .checked_add(length as u64)
352 .ok_or_else(|| VfError::client(0, libc::EOVERFLOW as u32))?;
353 }
354 })();
355 let close = file.close();
356 match operation {
357 Err(error) => Err(error),
358 Ok(completion) => close.map(|()| completion),
359 }
360 }
361}
362
363impl<F: MetadataFileSystem> FsClient<F> {
364 pub fn metadata(&self, path: impl AsRef<Path>) -> VfResult<Metadata> {
365 self.lock()?.metadata_path(path.as_ref(), true)
366 }
367
368 pub fn symlink_metadata(&self, path: impl AsRef<Path>) -> VfResult<Metadata> {
369 self.lock()?.metadata_path(path.as_ref(), false)
370 }
371
372 pub fn set_metadata(&self, path: impl AsRef<Path>) -> SetMetadata<'_, F> {
373 SetMetadata {
374 client: self,
375 path: path.as_ref().to_path_buf(),
376 update: MetadataUpdate::new(),
377 follow: true,
378 }
379 }
380}
381
382impl<F: DirectoryFileSystem> FsClient<F> {
383 pub fn create_dir(&self, path: impl AsRef<Path>) -> VfResult<()> {
384 self.create_dir_with_mode(path, 0o777)
385 }
386
387 pub fn create_dir_with_mode(&self, path: impl AsRef<Path>, mode: u32) -> VfResult<()> {
388 self.lock()?.create_dir_one(path.as_ref(), mode)
389 }
390
391 pub fn read_dir(&self, path: impl AsRef<Path>) -> VfResult<Vec<DirEntry>> {
392 self.read_dir_with_options(path, self.limits.directory_options())
393 }
394
395 pub fn read_dir_with_options(
397 &self,
398 path: impl AsRef<Path>,
399 options: ReadDirOptions,
400 ) -> VfResult<Vec<DirEntry>> {
401 self.lock()?.read_dir_one(path.as_ref(), options)
402 }
403
404 pub fn visit_dir(
408 &self,
409 path: impl AsRef<Path>,
410 callback: impl FnMut(DirEntry) -> VfResult<std::ops::ControlFlow<()>>,
411 ) -> VfResult<TraversalCompletion> {
412 self.visit_dir_with_options(path, self.limits.directory_options(), callback)
413 }
414
415 pub fn visit_dir_with_options(
417 &self,
418 path: impl AsRef<Path>,
419 options: ReadDirOptions,
420 mut callback: impl FnMut(DirEntry) -> VfResult<std::ops::ControlFlow<()>>,
421 ) -> VfResult<TraversalCompletion> {
422 const PAGE_SIZE: usize = 1024;
423 let path = path.as_ref();
424 let mut cursor = None;
425 let mut count = 0usize;
426 let mut path_bytes = 0usize;
427 let max_entries = if options.entry_limit() == usize::MAX {
428 0 } else {
430 options.entry_limit().saturating_add(1)
431 };
432 loop {
433 let page_size = if cursor.is_none() {
436 1
437 } else {
438 PAGE_SIZE.min(
439 options
440 .entry_limit()
441 .saturating_sub(count)
442 .saturating_add(1),
443 )
444 };
445 let (entries, next) = {
446 self.lock()?
447 .read_dir_page(path, cursor, page_size, max_entries)?
448 };
449 if entries.is_empty() && next.is_some() {
450 return Err(VfError::transport(None, "directory page made no progress"));
451 }
452 for entry in entries {
453 if count >= options.entry_limit() {
454 return Err(
455 VfError::failure(count, libc::EFBIG as u32).with_context("visit_dir", path)
456 );
457 }
458 path_bytes = path_bytes
459 .checked_add(entry.path().as_os_str().len())
460 .ok_or_else(|| {
461 VfError::failure(count, libc::EFBIG as u32).with_context("visit_dir", path)
462 })?;
463 if path_bytes > options.path_byte_limit() {
464 return Err(
465 VfError::failure(count, libc::EFBIG as u32).with_context("visit_dir", path)
466 );
467 }
468 count += 1;
469 if callback(entry)?.is_break() {
470 return Ok(TraversalCompletion::Stopped);
471 }
472 }
473 match next {
474 Some(value) => cursor = Some(value),
475 None => return Ok(TraversalCompletion::Complete),
476 }
477 }
478 }
479}
480
481impl<F: DirectoryFileSystem + MetadataFileSystem> FsClient<F> {
482 pub fn create_dir_all(&self, path: impl AsRef<Path>) -> VfResult<()> {
483 let path = path.as_ref();
484 let mut current = PathBuf::new();
485 for component in path.components() {
486 match component {
487 Component::RootDir => {
488 current.push(Path::new("/"));
489 continue;
490 }
491 Component::Normal(part) => current.push(part),
492 Component::CurDir => continue,
493 Component::ParentDir => {
494 current.push("..");
499 continue;
500 }
501 Component::Prefix(_) => return Err(VfError::client(0, crate::ERR_INVAL)),
502 }
503 match self.create_dir(¤t) {
504 Ok(()) => {}
505 Err(error) if error.err_no() == crate::ERR_EXIST => {
506 if !self.metadata(¤t)?.is_dir() {
507 return Err(VfError::client(0, crate::ERR_NOTDIR)
508 .with_context("create_dir_all", ¤t));
509 }
510 }
511 Err(error) => return Err(error),
512 }
513 }
514 Ok(())
515 }
516}
517
518impl<F: NamespaceFileSystem + MetadataFileSystem> FsClient<F> {
519 fn removal_metadata(&self, path: &Path, operation: &'static str) -> VfResult<Metadata> {
520 let mut filesystem = self.lock()?;
521 let follow = !filesystem.capabilities().contains(Capabilities::LSTAT);
522 filesystem
523 .metadata_path(path, follow)
524 .map_err(|error| error.with_context(operation, path))
525 }
526
527 pub fn remove_file(&self, path: impl AsRef<Path>) -> VfResult<()> {
528 let path = path.as_ref();
529 if self.removal_metadata(path, "remove_file")?.is_dir() {
530 return Err(VfError::client(0, crate::ERR_ISDIR).with_context("remove_file", path));
531 }
532 self.lock()?.remove_one(path, false)
533 }
534
535 pub fn remove_dir(&self, path: impl AsRef<Path>) -> VfResult<()> {
536 let path = path.as_ref();
537 if !self.removal_metadata(path, "remove_dir")?.is_dir() {
538 return Err(VfError::client(0, crate::ERR_NOTDIR).with_context("remove_dir", path));
539 }
540 self.lock()?.remove_one(path, false)
541 }
542
543 pub fn remove_dir_all(&self, path: impl AsRef<Path>) -> VfResult<()> {
544 let path = path.as_ref();
545 if !self.removal_metadata(path, "remove_dir_all")?.is_dir() {
546 return Err(VfError::client(0, crate::ERR_NOTDIR).with_context("remove_dir_all", path));
547 }
548 self.lock()?.remove_one(path, true)
549 }
550
551 pub fn remove_dir_contents(&self, path: impl AsRef<Path>) -> VfResult<()> {
553 let path = path.as_ref();
554 if !self.removal_metadata(path, "remove_dir_contents")?.is_dir() {
555 return Err(
556 VfError::client(0, crate::ERR_NOTDIR).with_context("remove_dir_contents", path)
557 );
558 }
559 self.lock()?.remove_dir_contents(path)
560 }
561
562 pub fn rename(&self, from: impl AsRef<Path>, to: impl AsRef<Path>) -> VfResult<()> {
563 self.lock()?.rename_one(from.as_ref(), to.as_ref())
564 }
565}
566
567impl<F: VecFs> FsClient<F> {
568 pub fn walk_events_with_options(
573 &self,
574 root: impl AsRef<Path>,
575 fields: AttrMask,
576 options: crate::WalkOptions,
577 sort_by_name: bool,
578 callback: impl FnMut(&crate::WalkEvent) -> VfResult<crate::WalkControl>,
579 ) -> VfResult<TraversalCompletion> {
580 let root = root.as_ref();
581 let metadata = self.symlink_metadata_with_fields(root, fields | AttrMask::MODE)?;
582 crate::walk_events(
583 DirEntry::new(root.to_path_buf(), metadata),
584 options,
585 sort_by_name,
586 |path, limits| {
587 let mut listings =
588 self.read_dirs_with_options(&[path], fields | AttrMask::MODE, limits)?;
589 if listings.len() != 1 {
590 return Err(VfError::transport(None, "invalid directory result count"));
591 }
592 Ok(listings.remove(0).entries)
593 },
594 callback,
595 )
596 }
597 pub fn visit_walk(
606 &self,
607 root: impl AsRef<Path>,
608 callback: impl FnMut(&DirEntry) -> VfResult<std::ops::ControlFlow<()>>,
609 ) -> VfResult<TraversalCompletion> {
610 self.visit_walk_with_options(root, self.limits.walk_options(), callback)
611 }
612
613 pub fn visit_walk_with_options(
614 &self,
615 root: impl AsRef<Path>,
616 options: crate::WalkOptions,
617 callback: impl FnMut(&DirEntry) -> VfResult<std::ops::ControlFlow<()>>,
618 ) -> VfResult<TraversalCompletion> {
619 let root = root.as_ref();
620 if !self.symlink_metadata(root)?.is_dir() {
621 return Err(VfError::client(0, crate::ERR_NOTDIR).with_context("visit_walk", root));
622 }
623 visit_walk_pages(
624 root,
625 options,
626 |path, cursor, page_size, max_entries| {
627 self.lock()?
628 .read_dir_page(path, cursor, page_size, max_entries)
629 },
630 callback,
631 )
632 }
633 pub fn read_dirs<P: AsRef<Path>>(&self, paths: &[P]) -> VfResult<Vec<DirectoryListing>> {
636 self.read_dirs_with_options(paths, AttrMask::stat(), self.limits.directory_options())
637 }
638}
639
640type DirectoryPage = (Vec<DirEntry>, Option<crate::DirPageCursor>);
641
642fn visit_walk_pages(
643 root: &Path,
644 options: crate::WalkOptions,
645 mut read_page: impl FnMut(
646 &Path,
647 Option<crate::DirPageCursor>,
648 usize,
649 usize,
650 ) -> VfResult<DirectoryPage>,
651 mut callback: impl FnMut(&DirEntry) -> VfResult<std::ops::ControlFlow<()>>,
652) -> VfResult<TraversalCompletion> {
653 let mut pending = vec![(root.to_path_buf(), 0usize)];
654 let mut count = 0usize;
655 let mut path_bytes = root.as_os_str().len();
656 if path_bytes > options.path_byte_limit() {
657 return Err(VfError::client(0, libc::EFBIG as u32));
658 }
659 while let Some((path, depth)) = pending.pop() {
660 let mut cursor = None;
661 let mut children = Vec::new();
662 loop {
663 let requested = options
664 .entry_limit()
665 .saturating_sub(count)
666 .saturating_add(1);
667 let (entries, next) = read_page(&path, cursor, requested.min(128), requested)?;
668 if entries.is_empty() && next.is_some() {
669 return Err(VfError::transport(None, "directory page made no progress")
670 .with_context("visit_walk", &path));
671 }
672 for entry in entries {
673 count = count
674 .checked_add(1)
675 .ok_or_else(|| VfError::client(0, libc::EFBIG as u32))?;
676 path_bytes = path_bytes
677 .checked_add(entry.path().as_os_str().len())
678 .ok_or_else(|| VfError::client(0, libc::EFBIG as u32))?;
679 if count > options.entry_limit() || path_bytes > options.path_byte_limit() {
680 return Err(
681 VfError::client(0, libc::EFBIG as u32).with_context("visit_walk", &path)
682 );
683 }
684 if callback(&entry)?.is_break() {
685 return Ok(TraversalCompletion::Stopped);
686 }
687 if entry.metadata().is_dir() {
688 if depth >= options.depth_limit() {
689 if !options.truncates_at_depth_limit() {
690 return Err(VfError::client(0, libc::EFBIG as u32)
691 .with_context("visit_walk", entry.path()));
692 }
693 } else {
694 children.push((entry.path().to_path_buf(), depth + 1));
695 }
696 }
697 }
698 cursor = next;
699 if cursor.is_none() {
700 break;
701 }
702 }
703 pending.extend(children.into_iter().rev());
704 }
705 Ok(TraversalCompletion::Complete)
706}
707
708impl<F: VecFs> FsClient<F> {
709 pub fn walk(&self, root: impl AsRef<Path>) -> VfResult<Vec<DirectoryListing>> {
712 self.walk_with_options(root, AttrMask::stat(), self.limits.walk_options())
713 }
714
715 pub fn ensure_empty_dir(&self, path: impl AsRef<Path>) -> VfResult<()> {
718 self.lock()?.ensure_empty_dir(path.as_ref())
719 }
720
721 pub fn remove_dir_all_with_options(
723 &self,
724 path: impl AsRef<Path>,
725 options: RemoveOptions,
726 ) -> VfResult<()> {
727 let path = path.as_ref();
728 if !self.removal_metadata(path, "remove_dir_all")?.is_dir() {
729 return Err(VfError::client(0, crate::ERR_NOTDIR).with_context("remove_dir_all", path));
730 }
731 self.lock()?
732 .rm_with_options(&[path], true, options)
733 .map_err(|error| error.with_context("remove_dir_all", path))
734 }
735
736 pub fn remove_dir_contents_with_options(
738 &self,
739 path: impl AsRef<Path>,
740 options: RemoveOptions,
741 ) -> VfResult<()> {
742 let path = path.as_ref();
743 if !self.removal_metadata(path, "remove_dir_contents")?.is_dir() {
744 return Err(
745 VfError::client(0, crate::ERR_NOTDIR).with_context("remove_dir_contents", path)
746 );
747 }
748 self.lock()?
749 .rm_contents_with_options(path, options)
750 .map_err(|error| error.with_context("remove_dir_contents", path))
751 }
752
753 pub fn open_dir_handle(&self, path: impl AsRef<Path>) -> VfResult<FsDir<F>> {
757 let path = path.as_ref();
758 let mut backend = self.lock()?;
759 let dir = backend.open_dir(path)?;
760 if !matches!(dir, VfDir::Descriptor { .. }) {
761 let _ = backend.close_dir(&dir);
762 return Err(VfError::unsupported(0).with_context("open_dir_handle", path));
763 }
764 Ok(FsDir {
765 inner: Arc::clone(&self.inner),
766 dir: Some(dir),
767 path: path.to_path_buf(),
768 })
769 }
770}
771
772pub struct FsDir<F: VecFs> {
775 inner: Arc<Mutex<F>>,
776 dir: Option<VfDir>,
777 path: PathBuf,
778}
779
780impl<F: VecFs> fmt::Debug for FsDir<F> {
781 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
782 formatter
783 .debug_struct("FsDir")
784 .field("path", &self.path)
785 .finish_non_exhaustive()
786 }
787}
788
789impl<F: VecFs> FsDir<F> {
790 pub fn is_closed(&self) -> bool {
791 self.dir.is_none()
792 }
793 pub fn path(&self) -> &Path {
795 &self.path
796 }
797
798 pub fn remove_contents(&self) -> VfResult<()> {
799 self.remove_contents_with_options(RemoveOptions::default())
800 }
801
802 pub fn remove_contents_with_options(&self, options: RemoveOptions) -> VfResult<()> {
803 let dir = self
804 .dir
805 .as_ref()
806 .ok_or_else(|| VfError::client(0, crate::ERR_EBADF))?;
807 self.inner
808 .lock()
809 .map_err(|_| poisoned())?
810 .rm_dir_contents_with_options(dir, options)
811 .map_err(|error| error.with_context("remove_dir_contents", &self.path))
812 }
813
814 pub fn close(mut self) -> VfResult<()> {
815 self.try_close()
816 }
817
818 pub fn try_close(&mut self) -> VfResult<()> {
820 let Some(dir) = self.dir.as_ref() else {
821 return Ok(());
822 };
823 self.inner.lock().map_err(|_| poisoned())?.close_dir(dir)?;
824 self.dir = None;
825 Ok(())
826 }
827}
828
829impl<F: VecFs> Drop for FsDir<F> {
830 fn drop(&mut self) {
831 if let Some(dir) = self.dir.take()
832 && let Ok(mut backend) = self.inner.lock()
833 {
834 let _ = backend.close_dir(&dir);
835 }
836 }
837}
838
839impl<F: LinkFileSystem> FsClient<F> {
840 pub fn symlink(&self, target: impl AsRef<Path>, link: impl AsRef<Path>) -> VfResult<()> {
841 self.lock()?.symlink_one(target.as_ref(), link.as_ref())
842 }
843
844 pub fn hard_link(&self, source: impl AsRef<Path>, link: impl AsRef<Path>) -> VfResult<()> {
845 self.lock()?.hard_link_one(source.as_ref(), link.as_ref())
846 }
847
848 pub fn read_link(&self, path: impl AsRef<Path>) -> VfResult<PathBuf> {
849 self.lock()?.read_link_one(path.as_ref())
850 }
851}
852
853impl<F: CopyFileSystem> FsClient<F> {
854 pub fn copy(&self, source: impl AsRef<Path>, destination: impl AsRef<Path>) -> VfResult<()> {
855 self.lock()?.copy_one(source.as_ref(), destination.as_ref())
856 }
857}
858
859impl<F: VecFs> FsClient<F> {
860 pub fn symlink_metadata_with_fields(
863 &self,
864 path: impl AsRef<Path>,
865 fields: AttrMask,
866 ) -> VfResult<Metadata> {
867 let path = path.as_ref();
868 let mut attrs = crate::VfAttrs {
869 file: VfFile::from_os_path(path),
870 masks: fields | AttrMask::MODE | AttrMask::SIZE,
871 ..crate::VfAttrs::default()
872 };
873 self.lock()?
874 .lgetattrsv(std::slice::from_mut(&mut attrs))
875 .map_err(|error| error.with_context("symlink_metadata", path))?;
876 Ok(vfsi_core::metadata_from_attrs(attrs))
877 }
878
879 pub fn read_dirs_with_options<P: AsRef<Path>>(
886 &self,
887 paths: &[P],
888 fields: AttrMask,
889 options: ReadDirOptions,
890 ) -> VfResult<Vec<DirectoryListing>> {
891 if paths.is_empty() {
892 return Ok(Vec::new());
893 }
894 let paths: Vec<&Path> = paths.iter().map(AsRef::as_ref).collect();
895 let mut positions: HashMap<PathBuf, Vec<usize>> = HashMap::with_capacity(paths.len());
896 let mut unique_paths = Vec::with_capacity(paths.len());
897 for (index, path) in paths.iter().enumerate() {
898 if let Some(indices) = positions.get_mut(*path) {
899 indices.push(index);
900 } else {
901 positions.insert(path.to_path_buf(), vec![index]);
902 unique_paths.push(*path);
903 }
904 }
905 let mut listings: Vec<DirectoryListing> = paths
906 .iter()
907 .map(|path| DirectoryListing {
908 path: path.to_path_buf(),
909 entries: Vec::new(),
910 })
911 .collect();
912 let mut entry_count = 0usize;
913 let mut path_bytes = 0usize;
914 const DIRECTORY_COHORT: usize = 32;
917 for cohort in unique_paths.chunks(DIRECTORY_COHORT) {
918 let mut callback_error = None;
919 let requested = options
920 .entry_limit()
921 .saturating_sub(entry_count)
922 .saturating_add(1);
923 let result = self.lock()?.listdirv(
924 cohort,
925 fields | AttrMask::MODE | AttrMask::SIZE,
926 requested,
927 false,
928 &mut |attributes, directory| {
929 let Some(indices) = positions.get(directory) else {
930 callback_error = Some(VfError::transport(
931 None,
932 format!(
933 "read_dirs backend returned an unexpected directory: {}",
934 directory.display()
935 ),
936 ));
937 return false;
938 };
939 if !cohort.contains(&directory) {
940 callback_error = Some(VfError::transport(
941 None,
942 "read_dirs backend returned a directory outside the active cohort",
943 ));
944 return false;
945 }
946 let Some(path) = attributes.file.path() else {
947 callback_error = Some(VfError::transport(
948 None,
949 "read_dirs backend returned an entry without a path",
950 ));
951 return false;
952 };
953 if path.parent() != Some(directory) {
954 callback_error = Some(VfError::transport(
955 None,
956 format!(
957 "read_dirs backend returned an entry outside {}",
958 directory.display()
959 ),
960 ));
961 return false;
962 }
963 for &index in indices {
964 entry_count += 1;
965 path_bytes = path_bytes.saturating_add(path.as_os_str().len());
966 if entry_count > options.entry_limit()
967 || path_bytes > options.path_byte_limit()
968 {
969 callback_error = Some(
970 VfError::failure(index, libc::EFBIG as u32)
971 .with_context("read_dirs", directory),
972 );
973 return false;
974 }
975 listings[index].entries.push(DirEntry::new(
976 path.to_path_buf(),
977 vfsi_core::metadata_from_attrs(attributes.clone()),
978 ));
979 }
980 true
981 },
982 );
983 if let Some(error) = callback_error {
984 return Err(error);
985 }
986 result.map_err(|error| {
987 error.map_index(|index| {
988 cohort
989 .get(index)
990 .and_then(|path| positions.get(*path))
991 .and_then(|indices| indices.first())
992 .copied()
993 .unwrap_or(index)
994 })
995 })?;
996 }
997 Ok(listings)
998 }
999
1000 pub fn walk_with_options(
1004 &self,
1005 root: impl AsRef<Path>,
1006 fields: AttrMask,
1007 options: crate::WalkOptions,
1008 ) -> VfResult<Vec<DirectoryListing>> {
1009 let root = root.as_ref();
1010 let tree = self.lock()?.walk_with_options(
1011 root,
1012 fields | AttrMask::MODE | AttrMask::SIZE,
1013 options,
1014 &mut |_, _| {},
1015 )?;
1016 tree.into_iter()
1017 .map(|directory| {
1018 let entries = directory
1019 .entries
1020 .into_iter()
1021 .enumerate()
1022 .map(|(index, attributes)| {
1023 let path = attributes
1024 .file
1025 .path()
1026 .ok_or_else(|| {
1027 VfError::transport(
1028 None,
1029 format!("walk backend returned entry {index} without a path"),
1030 )
1031 })?
1032 .to_path_buf();
1033 Ok(DirEntry::new(
1034 path,
1035 vfsi_core::metadata_from_attrs(attributes),
1036 ))
1037 })
1038 .collect::<VfResult<Vec<_>>>()?;
1039 Ok(DirectoryListing {
1040 path: directory.path,
1041 entries,
1042 })
1043 })
1044 .collect()
1045 }
1046
1047 pub fn copy_files<P: AsRef<Path>, Q: AsRef<Path>>(&self, pairs: &[(P, Q)]) -> VfResult<()> {
1050 let extents: Vec<_> = pairs
1051 .iter()
1052 .map(|(from, to)| {
1053 crate::ExtentPair::from_os_paths(from.as_ref(), 0, to.as_ref(), 0, None)
1054 })
1055 .collect();
1056 self.lock()?.copyv(&extents).map_err(|error| {
1057 error
1058 .index()
1059 .and_then(|index| pairs.get(index))
1060 .map_or(error.clone(), |(_, to)| {
1061 error.with_context("copy_files", to.as_ref())
1062 })
1063 })
1064 }
1065
1066 pub fn remove_paths<P: AsRef<Path>>(&self, paths: &[P], recursive: bool) -> VfResult<()> {
1069 self.remove_paths_with_options(paths, recursive, RemoveOptions::default())
1070 }
1071
1072 pub fn remove_paths_with_options<P: AsRef<Path>>(
1074 &self,
1075 paths: &[P],
1076 recursive: bool,
1077 options: RemoveOptions,
1078 ) -> VfResult<()> {
1079 let paths: Vec<&Path> = paths.iter().map(AsRef::as_ref).collect();
1080 self.lock()?
1081 .rm_with_options(&paths, recursive, options)
1082 .map_err(|error| {
1083 error
1084 .index()
1085 .and_then(|index| paths.get(index))
1086 .map_or(error.clone(), |path| {
1087 error.with_context("remove_paths", path)
1088 })
1089 })
1090 }
1091
1092 pub fn symlink_metadatav(&self, paths: &[&Path]) -> VfResult<Vec<Metadata>> {
1095 let mut attrs: Vec<_> = paths
1096 .iter()
1097 .map(|path| crate::VfAttrs {
1098 file: VfFile::from_os_path(path),
1099 masks: AttrMask::MODE,
1100 ..crate::VfAttrs::default()
1101 })
1102 .collect();
1103 self.lock()?.lgetattrsv(&mut attrs).map_err(|error| {
1104 error
1105 .index()
1106 .and_then(|index| paths.get(index))
1107 .map_or(error.clone(), |path| {
1108 error.with_context("symlink_metadatav", path)
1109 })
1110 })?;
1111 Ok(attrs
1112 .into_iter()
1113 .map(vfsi_core::metadata_from_attrs)
1114 .collect())
1115 }
1116}
1117
1118impl<F: VectorFileSystem> FsClient<F> {
1119 pub fn openv(&self, requests: &[OpenRequest]) -> VfResult<Vec<FsFile<F>>> {
1125 let mut filesystem = self.lock()?;
1126 let files = filesystem.open_many(requests).map_err(|error| {
1127 error
1128 .index()
1129 .and_then(|index| requests.get(index))
1130 .map_or(error.clone(), |request| {
1131 error.with_context("openv", &request.path)
1132 })
1133 })?;
1134 if files.len() != requests.len() {
1135 let error = wrong_result_count("openv", requests.len(), files.len());
1136 for file in &files {
1137 let _ = filesystem.close_one(file);
1138 }
1139 return Err(error);
1140 }
1141 drop(filesystem);
1142 Ok(files
1143 .into_iter()
1144 .zip(requests)
1145 .map(|(file, request)| FsFile {
1146 inner: Arc::clone(&self.inner),
1147 file: Some(file),
1148 path: request.path.clone(),
1149 })
1150 .collect())
1151 }
1152
1153 pub fn try_closev<'a>(&self, files: impl IntoIterator<Item = &'a mut FsFile<F>>) -> VfResult<()>
1157 where
1158 F: 'a,
1159 {
1160 let mut files: Vec<_> = files.into_iter().collect();
1161 for (index, file) in files.iter().enumerate() {
1162 self.validate_owner(file, index)?;
1163 }
1164 let positions: Vec<_> = files
1165 .iter()
1166 .enumerate()
1167 .filter_map(|(index, file)| (!file.is_closed()).then_some(index))
1168 .collect();
1169 files.retain(|file| !file.is_closed());
1170 if files.is_empty() {
1171 return Ok(());
1172 }
1173 let descriptors: Vec<VfFile> = files
1174 .iter()
1175 .map(|file| file.raw().cloned())
1176 .collect::<VfResult<_>>()?;
1177 self.lock()?.close_many(&descriptors).map_err(|error| {
1178 error
1179 .index()
1180 .and_then(|index| files.get(index))
1181 .map_or(error.clone(), |file| {
1182 error.with_context("closev", file.path())
1183 })
1184 .map_index(|index| positions.get(index).copied().unwrap_or(index))
1185 })?;
1186 for file in &mut files {
1187 file.file = None;
1188 }
1189 Ok(())
1190 }
1191
1192 pub fn closev(&self, mut files: Vec<FsFile<F>>) -> VfResult<()> {
1198 self.try_closev(&mut files)
1199 }
1200
1201 pub fn readv(&self, requests: &[FsRead<'_, F>]) -> VfResult<Vec<FsReadResult>> {
1205 self.readv_with_limit(requests, self.limits.max_read_bytes)
1206 }
1207
1208 pub fn readv_with_limit(
1210 &self,
1211 requests: &[FsRead<'_, F>],
1212 max_total_bytes: usize,
1213 ) -> VfResult<Vec<FsReadResult>> {
1214 self.readv_with_limit_projected(requests, max_total_bytes, |request| request)
1215 }
1216
1217 pub fn readv_with_limit_projected<'b, T>(
1220 &self,
1221 requests: &[T],
1222 max_total_bytes: usize,
1223 project: impl for<'r> Fn(&'r T) -> &'r FsRead<'b, F>,
1224 ) -> VfResult<Vec<FsReadResult>>
1225 where
1226 F: 'b,
1227 {
1228 let mut requested = 0usize;
1229 for (index, request) in requests.iter().map(&project).enumerate() {
1230 requested = requested
1231 .checked_add(request.length)
1232 .ok_or_else(|| VfError::client(index, libc::EFBIG as u32))?;
1233 if requested > max_total_bytes {
1234 return Err(VfError::client(index, libc::EFBIG as u32)
1235 .with_context("readv", request.file.path()));
1236 }
1237 }
1238 let reads = self.read_ops(requests, &project)?;
1239 let results = self.lock()?.read_many(&reads).map_err(|error| {
1240 error
1241 .index()
1242 .and_then(|index| requests.get(index).map(&project))
1243 .map_or(error.clone(), |request| {
1244 error.with_context("readv", request.file.path())
1245 })
1246 })?;
1247 if results.len() != requests.len() {
1248 return Err(wrong_result_count("readv", requests.len(), results.len()));
1249 }
1250 validate_read_results("readv", &reads, &results).map_err(|error| {
1251 error
1252 .index()
1253 .and_then(|index| requests.get(index).map(&project))
1254 .map_or(error.clone(), |request| {
1255 error.with_context("readv", request.file.path())
1256 })
1257 })?;
1258 Ok(results.into_iter().map(read_result).collect())
1259 }
1260
1261 fn read_ops<'b, T>(
1262 &self,
1263 requests: &[T],
1264 project: impl for<'r> Fn(&'r T) -> &'r FsRead<'b, F>,
1265 ) -> VfResult<Vec<ReadOp>>
1266 where
1267 F: 'b,
1268 {
1269 let mut reads = Vec::with_capacity(requests.len());
1270 for (index, request) in requests.iter().map(&project).enumerate() {
1271 self.validate_owner(request.file, index)?;
1272 reads.push(ReadOp::new(
1273 request.file.raw()?.clone(),
1274 request.offset,
1275 request.length,
1276 ));
1277 }
1278 Ok(reads)
1279 }
1280
1281 pub fn readv_into(
1282 &self,
1283 requests: &mut [FsReadInto<'_, F>],
1284 ) -> VfResult<Vec<FsReadIntoResult>> {
1285 self.readv_into_with_limit(requests, self.limits.max_read_bytes)
1286 }
1287
1288 pub fn readv_into_with_limit(
1291 &self,
1292 requests: &mut [FsReadInto<'_, F>],
1293 max_bytes: usize,
1294 ) -> VfResult<Vec<FsReadIntoResult>> {
1295 self.readv_into_with_limit_projected(
1296 requests,
1297 max_bytes,
1298 |request| request,
1299 |request| request,
1300 )
1301 }
1302
1303 pub fn readv_into_with_limit_projected<'b, T>(
1307 &self,
1308 requests: &mut [T],
1309 max_bytes: usize,
1310 project: impl for<'r> Fn(&'r T) -> &'r FsReadInto<'b, F>,
1311 mut project_mut: impl for<'r> FnMut(&'r mut T) -> &'r mut FsReadInto<'b, F>,
1312 ) -> VfResult<Vec<FsReadIntoResult>>
1313 where
1314 F: 'b,
1315 {
1316 let mut requested = 0usize;
1317 let mut reads = Vec::with_capacity(requests.len());
1318 for (index, request) in requests.iter().map(&project).enumerate() {
1319 self.validate_owner(request.file, index)?;
1320 requested = requested
1321 .checked_add(request.buffer.len())
1322 .ok_or_else(|| VfError::client(index, libc::EFBIG as u32))?;
1323 if requested > max_bytes {
1324 return Err(VfError::client(index, libc::EFBIG as u32));
1325 }
1326 reads.push(ReadOp::new(
1327 request.file.raw()?.clone(),
1328 request.offset,
1329 request.buffer.len(),
1330 ));
1331 }
1332 let results = {
1333 let mut buffers: Vec<&mut [u8]> = requests
1334 .iter_mut()
1335 .map(&mut project_mut)
1336 .map(|request| &mut *request.buffer)
1337 .collect();
1338 self.lock()?.read_many_into(&reads, &mut buffers)
1339 }
1340 .map_err(|error| {
1341 error
1342 .index()
1343 .and_then(|index| requests.get(index).map(&project))
1344 .map_or(error.clone(), |request| {
1345 error.with_context("readv_into", request.file.path())
1346 })
1347 })?;
1348 validate_read_into_results("readv_into", &reads, &results).map_err(|error| {
1349 error
1350 .index()
1351 .and_then(|index| requests.get(index).map(&project))
1352 .map_or(error.clone(), |request| {
1353 error.with_context("readv_into", request.file.path())
1354 })
1355 })?;
1356 Ok(results
1357 .into_iter()
1358 .map(|result| FsReadIntoResult {
1359 offset: result.offset,
1360 read: result.read,
1361 eof: result.eof,
1362 })
1363 .collect())
1364 }
1365
1366 pub fn writev(&self, requests: &[FsWrite<'_, F>]) -> VfResult<Vec<FsWriteResult>> {
1367 self.writev_projected(requests, |request| request)
1368 }
1369
1370 pub fn writev_projected<'b, T>(
1373 &self,
1374 requests: &[T],
1375 project: impl for<'r> Fn(&'r T) -> &'r FsWrite<'b, F>,
1376 ) -> VfResult<Vec<FsWriteResult>>
1377 where
1378 F: 'b,
1379 {
1380 let writes = self.write_ops(requests, &project)?;
1381 let results = self.lock()?.write_many(&writes).map_err(|error| {
1382 error
1383 .index()
1384 .and_then(|index| requests.get(index).map(&project))
1385 .map_or(error.clone(), |request| {
1386 error.with_context("writev", request.file.path())
1387 })
1388 })?;
1389 if results.len() != requests.len() {
1390 return Err(wrong_result_count("writev", requests.len(), results.len()));
1391 }
1392 validate_write_results("writev", &writes, &results).map_err(|error| {
1393 error
1394 .index()
1395 .and_then(|index| requests.get(index).map(&project))
1396 .map_or(error.clone(), |request| {
1397 error.with_context("writev", request.file.path())
1398 })
1399 })?;
1400 Ok(results.into_iter().map(write_result).collect())
1401 }
1402
1403 pub fn write_allv(&self, requests: &[FsWrite<'_, F>]) -> VfResult<Vec<FsWriteResult>> {
1409 self.write_allv_projected(requests, |request| request)
1410 }
1411
1412 pub fn write_allv_projected<'b, T>(
1415 &self,
1416 requests: &[T],
1417 project: impl for<'r> Fn(&'r T) -> &'r FsWrite<'b, F>,
1418 ) -> VfResult<Vec<FsWriteResult>>
1419 where
1420 F: 'b,
1421 {
1422 let mut prior_by_path: HashMap<&Path, Vec<(usize, u64, u64)>> = HashMap::new();
1425 let mut blocked_by = vec![Vec::new(); requests.len()];
1426 for (index, request) in requests.iter().map(&project).enumerate() {
1427 self.validate_owner(request.file, index)?;
1428 request.file.raw()?;
1429 let VfOffset::At(offset) = request.offset else {
1430 return Err(VfError::client(index, crate::ERR_INVAL)
1431 .with_context("write_allv", request.file.path()));
1432 };
1433 let end = offset
1434 .checked_add(request.data.len() as u64)
1435 .ok_or_else(|| {
1436 VfError::client(index, libc::EOVERFLOW as u32)
1437 .with_context("write_allv", request.file.path())
1438 })?;
1439 let prior = prior_by_path.entry(request.file.path()).or_default();
1443 for &(earlier, start, earlier_end) in prior.iter() {
1444 if offset < earlier_end && start < end {
1445 blocked_by[index].push(earlier);
1446 }
1447 }
1448 prior.push((index, offset, end));
1449 }
1450 let mut totals = vec![0usize; requests.len()];
1451 let mut stable = vec![true; requests.len()];
1452 let mut pending: Vec<usize> = (0..requests.len())
1453 .filter(|&index| !project(&requests[index]).data.is_empty())
1454 .collect();
1455 while !pending.is_empty() {
1456 let wave_indices: Vec<usize> = pending
1457 .iter()
1458 .copied()
1459 .filter(|&index| {
1460 blocked_by[index]
1461 .iter()
1462 .all(|&earlier| totals[earlier] == project(&requests[earlier]).data.len())
1463 })
1464 .collect();
1465 let wave: Vec<_> = wave_indices
1466 .iter()
1467 .map(|&index| {
1468 let request = project(&requests[index]);
1469 let VfOffset::At(offset) = request.offset else {
1470 return Err(VfError::client(index, crate::ERR_INVAL)
1471 .with_context("write_allv", request.file.path()));
1472 };
1473 let offset = offset.checked_add(totals[index] as u64).ok_or_else(|| {
1474 VfError::client(index, libc::EOVERFLOW as u32)
1475 .with_context("write_allv", request.file.path())
1476 })?;
1477 Ok(FsWrite {
1478 file: request.file,
1479 offset: VfOffset::At(offset),
1480 data: &request.data[totals[index]..],
1481 })
1482 })
1483 .collect::<VfResult<_>>()?;
1484 let results = self.writev(&wave).map_err(|error| {
1485 error.map_index(|index| wave_indices.get(index).copied().unwrap_or(index))
1486 })?;
1487 for (&index, result) in wave_indices.iter().zip(results) {
1488 if result.written == 0 {
1489 return Err(VfError::client(index, crate::ERR_IO)
1490 .with_context("write_allv", project(&requests[index]).file.path()));
1491 }
1492 totals[index] += result.written;
1493 stable[index] &= result.stable;
1494 }
1495 pending.retain(|&index| totals[index] < project(&requests[index]).data.len());
1496 }
1497 requests
1498 .iter()
1499 .map(&project)
1500 .enumerate()
1501 .map(|(index, request)| {
1502 let VfOffset::At(offset) = request.offset else {
1503 return Err(VfError::client(index, crate::ERR_INVAL)
1504 .with_context("write_allv", request.file.path()));
1505 };
1506 Ok(FsWriteResult {
1507 offset,
1508 written: totals[index],
1509 stable: stable[index],
1510 })
1511 })
1512 .collect()
1513 }
1514
1515 fn write_ops<'a, 'b, T>(
1516 &self,
1517 requests: &'a [T],
1518 project: impl for<'r> Fn(&'r T) -> &'r FsWrite<'b, F>,
1519 ) -> VfResult<Vec<WriteOpRef<'a>>>
1520 where
1521 F: 'b,
1522 'b: 'a,
1523 {
1524 let mut writes = Vec::with_capacity(requests.len());
1525 for (index, request) in requests.iter().map(&project).enumerate() {
1526 self.validate_owner(request.file, index)?;
1527 writes.push(WriteOpRef::new(
1528 request.file.raw()?,
1529 request.offset,
1530 request.data,
1531 ));
1532 }
1533 Ok(writes)
1534 }
1535
1536 fn validate_owner(&self, file: &FsFile<F>, index: usize) -> VfResult<()> {
1537 if Arc::ptr_eq(&self.inner, &file.inner) {
1538 Ok(())
1539 } else {
1540 Err(VfError::client(index, crate::ERR_INVAL))
1541 }
1542 }
1543}
1544
1545impl<F: VectorFileSystem + VecFs> FsClient<F> {
1546 pub fn read_files<P: AsRef<Path>>(&self, paths: &[P]) -> VfResult<Vec<Vec<u8>>> {
1553 self.read_files_with_options(
1554 paths,
1555 ReadAllOptions::new().max_total_bytes(self.limits.max_read_bytes),
1556 )
1557 }
1558
1559 pub fn read_files_with_options<P: AsRef<Path>>(
1561 &self,
1562 paths: &[P],
1563 options: ReadAllOptions,
1564 ) -> VfResult<Vec<Vec<u8>>> {
1565 let files: Vec<_> = paths
1566 .iter()
1567 .map(|path| VfFile::from_os_path(path.as_ref()))
1568 .collect();
1569 let result = self
1570 .lock()?
1571 .read_allv_with_options(&files, options)
1572 .map_err(|error| {
1573 error
1574 .index()
1575 .and_then(|index| paths.get(index))
1576 .map_or(error.clone(), |path| {
1577 error.with_context("read_files", path.as_ref())
1578 })
1579 });
1580 result.and_then(|buffers| {
1581 if buffers.len() != paths.len() {
1582 Err(wrong_result_count("read_files", paths.len(), buffers.len()))
1583 } else {
1584 Ok(buffers)
1585 }
1586 })
1587 }
1588
1589 pub fn write_files<P: AsRef<Path>, B: AsRef<[u8]>>(&self, entries: &[(P, B)]) -> VfResult<()> {
1596 let mut seen = HashSet::with_capacity(entries.len());
1597 for (index, (path, _)) in entries.iter().enumerate() {
1598 if !seen.insert(path.as_ref()) {
1599 return Err(VfError::client(index, crate::ERR_INVAL)
1600 .with_context("write_files", path.as_ref()));
1601 }
1602 }
1603 let requests: Vec<_> = entries
1604 .iter()
1605 .map(|(path, _)| {
1606 OpenRequest::new(
1607 path.as_ref(),
1608 OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::TRUNCATE,
1609 )
1610 })
1611 .collect();
1612 let files = self.openv(&requests)?;
1613 let writes: Vec<_> = files
1614 .iter()
1615 .zip(entries)
1616 .map(|(file, (_, data))| file.write_request_at(0, data.as_ref()))
1617 .collect();
1618 let result = self.write_allv(&writes);
1619 drop(writes);
1620 let close_result = self.closev(files);
1621 result?;
1622 close_result
1623 }
1624}
1625
1626pub struct OpenOptions<'a, F: FileSystem> {
1628 client: &'a FsClient<F>,
1629 flags: OpenFlags,
1630 mode: u32,
1631}
1632
1633impl<F: FileSystem> Clone for OpenOptions<'_, F> {
1634 fn clone(&self) -> Self {
1635 Self {
1636 client: self.client,
1637 flags: self.flags,
1638 mode: self.mode,
1639 }
1640 }
1641}
1642
1643impl<F: FileSystem> fmt::Debug for OpenOptions<'_, F> {
1644 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1645 formatter
1646 .debug_struct("OpenOptions")
1647 .field("flags", &self.flags)
1648 .field("mode", &format_args!("{:#o}", self.mode))
1649 .finish()
1650 }
1651}
1652
1653impl<'a, F: FileSystem> OpenOptions<'a, F> {
1654 fn new(client: &'a FsClient<F>) -> Self {
1655 Self {
1656 client,
1657 flags: OpenFlags::empty(),
1658 mode: 0o666,
1659 }
1660 }
1661
1662 pub fn read(&mut self, enabled: bool) -> &mut Self {
1663 self.flags.set(OpenFlags::READ, enabled);
1664 self
1665 }
1666
1667 pub fn write(&mut self, enabled: bool) -> &mut Self {
1668 self.flags.set(OpenFlags::WRITE, enabled);
1669 self
1670 }
1671
1672 pub fn append(&mut self, enabled: bool) -> &mut Self {
1673 self.flags.set(OpenFlags::APPEND, enabled);
1674 self
1675 }
1676
1677 pub fn truncate(&mut self, enabled: bool) -> &mut Self {
1678 self.flags.set(OpenFlags::TRUNCATE, enabled);
1679 self
1680 }
1681
1682 pub fn create(&mut self, enabled: bool) -> &mut Self {
1683 self.flags.set(OpenFlags::CREATE, enabled);
1684 self
1685 }
1686
1687 pub fn create_new(&mut self, enabled: bool) -> &mut Self {
1688 self.flags.set(OpenFlags::CREATE_NEW, enabled);
1689 self
1690 }
1691
1692 pub fn mode(&mut self, mode: u32) -> &mut Self {
1693 self.mode = mode;
1694 self
1695 }
1696
1697 pub fn open(&self, path: impl AsRef<Path>) -> VfResult<FsFile<F>> {
1698 self.client
1699 .open_with(OpenRequest::new(path.as_ref(), self.flags).mode(self.mode))
1700 }
1701}
1702
1703impl<F: VectorFileSystem> OpenOptions<'_, F> {
1704 pub fn openv<P: AsRef<Path>>(&self, paths: &[P]) -> VfResult<Vec<FsFile<F>>> {
1705 let requests: Vec<OpenRequest> = paths
1706 .iter()
1707 .map(|path| OpenRequest::new(path.as_ref(), self.flags).mode(self.mode))
1708 .collect();
1709 self.client.openv(&requests)
1710 }
1711}
1712
1713pub struct SetMetadata<'a, F: MetadataFileSystem> {
1715 client: &'a FsClient<F>,
1716 path: PathBuf,
1717 update: MetadataUpdate,
1718 follow: bool,
1719}
1720
1721impl<F: MetadataFileSystem> Clone for SetMetadata<'_, F> {
1722 fn clone(&self) -> Self {
1723 Self {
1724 client: self.client,
1725 path: self.path.clone(),
1726 update: self.update.clone(),
1727 follow: self.follow,
1728 }
1729 }
1730}
1731
1732impl<F: MetadataFileSystem> fmt::Debug for SetMetadata<'_, F> {
1733 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1734 formatter
1735 .debug_struct("SetMetadata")
1736 .field("path", &self.path)
1737 .field("update", &self.update)
1738 .field("follow", &self.follow)
1739 .finish_non_exhaustive()
1740 }
1741}
1742
1743impl<F: MetadataFileSystem> SetMetadata<'_, F> {
1744 pub fn permissions(&mut self, permissions: Permissions) -> &mut Self {
1745 self.update.permissions = Some(permissions);
1746 self
1747 }
1748
1749 pub fn len(&mut self, len: u64) -> &mut Self {
1750 self.update.len = Some(len);
1751 self
1752 }
1753
1754 pub fn accessed(&mut self, accessed: SystemTime) -> &mut Self {
1755 self.update.accessed = Some(accessed);
1756 self
1757 }
1758
1759 pub fn modified(&mut self, modified: SystemTime) -> &mut Self {
1760 self.update.modified = Some(modified);
1761 self
1762 }
1763
1764 pub fn follow_symlinks(&mut self, follow: bool) -> &mut Self {
1765 self.follow = follow;
1766 self
1767 }
1768
1769 pub fn apply(&self) -> VfResult<()> {
1770 self.client
1771 .lock()?
1772 .set_metadata_path(&self.path, self.update.clone(), self.follow)
1773 }
1774}
1775
1776pub struct FsFile<F: FileSystem> {
1782 inner: Arc<Mutex<F>>,
1783 file: Option<VfFile>,
1784 path: PathBuf,
1785}
1786
1787impl<F: FileSystem> fmt::Debug for FsFile<F> {
1788 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1789 formatter
1790 .debug_struct("FsFile")
1791 .field("path", &self.path)
1792 .field("is_open", &self.file.is_some())
1793 .finish()
1794 }
1795}
1796
1797impl<F: FileSystem> FsFile<F> {
1798 pub fn is_closed(&self) -> bool {
1800 self.file.is_none()
1801 }
1802 fn raw(&self) -> VfResult<&VfFile> {
1803 self.file
1804 .as_ref()
1805 .ok_or_else(|| VfError::client(0, crate::ERR_EBADF))
1806 }
1807
1808 pub fn read_at(&self, buffer: &mut [u8], offset: u64) -> VfResult<usize> {
1810 self.read_into(buffer, VfOffset::At(offset))
1811 }
1812
1813 pub fn write_at(&self, buffer: &[u8], offset: u64) -> VfResult<usize> {
1815 self.write_from(buffer, VfOffset::At(offset))
1816 }
1817
1818 pub fn read_request_at(&self, offset: u64, length: usize) -> FsRead<'_, F> {
1819 FsRead {
1820 file: self,
1821 offset: VfOffset::At(offset),
1822 length,
1823 }
1824 }
1825
1826 pub fn path(&self) -> &Path {
1830 &self.path
1831 }
1832
1833 pub fn metadata(&self) -> VfResult<Metadata> {
1835 let attributes = AttrMask::MODE
1836 | AttrMask::SIZE
1837 | AttrMask::NLINK
1838 | AttrMask::FILEID
1839 | AttrMask::UID
1840 | AttrMask::GID
1841 | AttrMask::ATIME
1842 | AttrMask::MTIME
1843 | AttrMask::CTIME
1844 | AttrMask::CHANGE;
1845 self.inner
1846 .lock()
1847 .map_err(|_| poisoned())?
1848 .metadata(MetadataQuery::new(self.raw()?.clone(), attributes))
1849 .map(vfsi_core::metadata_from_attrs)
1850 .map_err(|error| error.with_context("metadata", &self.path))
1851 }
1852
1853 pub fn set_len(&self, len: u64) -> VfResult<()> {
1855 let mut update = SetAttributes::new(self.raw()?.clone());
1856 update.size = Some(len);
1857 self.inner
1858 .lock()
1859 .map_err(|_| poisoned())?
1860 .set_attributes(update)
1861 .map_err(|error| error.with_context("set_len", &self.path))
1862 }
1863
1864 pub fn set_permissions(&self, permissions: Permissions) -> VfResult<()> {
1866 let mut update = SetAttributes::new(self.raw()?.clone());
1867 update.mode = Some(permissions.mode());
1868 self.inner
1869 .lock()
1870 .map_err(|_| poisoned())?
1871 .set_attributes(update)
1872 .map_err(|error| error.with_context("set_permissions", &self.path))
1873 }
1874
1875 pub fn write_request_at<'a>(&'a self, offset: u64, data: &'a [u8]) -> FsWrite<'a, F> {
1876 FsWrite {
1877 file: self,
1878 offset: VfOffset::At(offset),
1879 data,
1880 }
1881 }
1882
1883 pub fn read_request_at_into<'a>(
1884 &'a self,
1885 offset: u64,
1886 buffer: &'a mut [u8],
1887 ) -> FsReadInto<'a, F> {
1888 FsReadInto {
1889 file: self,
1890 offset: VfOffset::At(offset),
1891 buffer,
1892 }
1893 }
1894
1895 pub fn read_native(&mut self, buffer: &mut [u8]) -> VfResult<usize> {
1896 self.read_into(buffer, VfOffset::Cur)
1897 }
1898
1899 pub fn read_to_end_with_limit(&mut self, max_bytes: usize) -> VfResult<Vec<u8>> {
1907 let mut data = Vec::new();
1908 loop {
1909 let remaining = max_bytes - data.len();
1910 if remaining == 0 {
1911 let mut probe = [0_u8; 1];
1912 if self.read_native(&mut probe)? == 0 {
1913 return Ok(data);
1914 }
1915 return Err(VfError::client(0, libc::EFBIG as u32)
1916 .with_context("read_to_end_with_limit", &self.path));
1917 }
1918 let chunk = remaining.min(crate::DEFAULT_READ_STREAM_CHUNK_BYTES);
1919 data.try_reserve(chunk).map_err(|_| {
1920 VfError::client(0, libc::ENOMEM as u32)
1921 .with_context("read_to_end_with_limit", &self.path)
1922 })?;
1923 let start = data.len();
1924 data.resize(start + chunk, 0);
1925 let count = self.read_native(&mut data[start..])?;
1926 data.truncate(start + count);
1927 if count == 0 {
1928 return Ok(data);
1929 }
1930 }
1931 }
1932
1933 pub fn write_native(&mut self, buffer: &[u8]) -> VfResult<usize> {
1934 self.write_from(buffer, VfOffset::Cur)
1935 }
1936
1937 fn read_into(&self, buffer: &mut [u8], offset: VfOffset) -> VfResult<usize> {
1938 if buffer.is_empty() {
1939 return Ok(0);
1940 }
1941 let request = ReadOp::new(self.raw()?.clone(), offset, buffer.len());
1942 let result = self
1943 .inner
1944 .lock()
1945 .map_err(|_| poisoned())?
1946 .read_one_into(&request, buffer)
1947 .map_err(|error| error.with_context("read", &self.path))?;
1948 validate_read_into_results(
1949 "read",
1950 std::slice::from_ref(&request),
1951 std::slice::from_ref(&result),
1952 )
1953 .map_err(|error| error.with_context("read", &self.path))?;
1954 Ok(result.read)
1955 }
1956
1957 fn write_from(&self, buffer: &[u8], offset: VfOffset) -> VfResult<usize> {
1958 if buffer.is_empty() {
1959 return Ok(0);
1960 }
1961 let file = self.raw()?.clone();
1962 let result = self
1963 .inner
1964 .lock()
1965 .map_err(|_| poisoned())?
1966 .write_one(WriteOpRef::new(&file, offset, buffer))
1967 .map_err(|error| error.with_context("write", &self.path))?;
1968 if result.written > buffer.len() {
1969 return Err(VfError::client(0, crate::ERR_IO).with_context("write", &self.path));
1970 }
1971 Ok(result.written)
1972 }
1973
1974 pub fn sync_data(&self) -> VfResult<()> {
1975 self.inner
1976 .lock()
1977 .map_err(|_| poisoned())?
1978 .sync_data(self.raw()?)
1979 .map_err(|error| error.with_context("sync_data", &self.path))
1980 }
1981
1982 pub fn sync_all(&self) -> VfResult<()> {
1983 self.inner
1984 .lock()
1985 .map_err(|_| poisoned())?
1986 .sync_all(self.raw()?)
1987 .map_err(|error| error.with_context("sync_all", &self.path))
1988 }
1989
1990 pub fn try_close(&mut self) -> VfResult<()> {
1996 let Some(file) = self.file.as_ref() else {
1997 return Ok(());
1998 };
1999 self.inner
2000 .lock()
2001 .map_err(|_| poisoned())?
2002 .close_one(file)
2003 .map_err(|error| error.with_context("close", &self.path))?;
2004 self.file = None;
2005 Ok(())
2006 }
2007
2008 pub fn close(mut self) -> VfResult<()> {
2011 self.try_close()
2012 }
2013
2014 pub fn seek_native(&mut self, position: IoSeekFrom) -> VfResult<u64> {
2016 self.inner
2017 .lock()
2018 .map_err(|_| poisoned())?
2019 .seek_one(self.raw()?, position)
2020 .map_err(|error| error.with_context("seek", &self.path))
2021 }
2022}
2023
2024impl<F: FileSystem> Read for FsFile<F> {
2025 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
2026 self.read_native(buffer).map_err(io_error)
2027 }
2028}
2029
2030impl<F: FileSystem> Write for FsFile<F> {
2031 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
2032 self.write_native(buffer).map_err(io_error)
2033 }
2034
2035 fn flush(&mut self) -> io::Result<()> {
2036 self.sync_data().map_err(io_error)
2037 }
2038}
2039
2040impl<F: FileSystem> Seek for FsFile<F> {
2041 fn seek(&mut self, position: IoSeekFrom) -> io::Result<u64> {
2042 self.seek_native(position).map_err(io_error)
2043 }
2044}
2045
2046pub struct FsRead<'a, F: FileSystem> {
2048 file: &'a FsFile<F>,
2049 offset: VfOffset,
2050 length: usize,
2051}
2052
2053pub struct FsWrite<'a, F: FileSystem> {
2055 file: &'a FsFile<F>,
2056 offset: VfOffset,
2057 data: &'a [u8],
2058}
2059
2060pub struct FsReadInto<'a, F: FileSystem> {
2062 file: &'a FsFile<F>,
2063 offset: VfOffset,
2064 buffer: &'a mut [u8],
2065}
2066
2067impl<F: FileSystem> Drop for FsFile<F> {
2068 fn drop(&mut self) {
2069 if let Some(file) = self.file.take()
2070 && let Ok(mut filesystem) = self.inner.lock()
2071 {
2072 let _ = filesystem.close_one(&file);
2073 }
2074 }
2075}
2076
2077#[cfg(test)]
2078mod traversal_tests {
2079 use super::*;
2080 use std::sync::atomic::{AtomicUsize, Ordering};
2081
2082 struct Snapshot {
2085 remaining: std::vec::IntoIter<DirEntry>,
2086 live: Arc<AtomicUsize>,
2087 }
2088 impl Drop for Snapshot {
2089 fn drop(&mut self) {
2090 self.live.fetch_sub(1, Ordering::SeqCst);
2091 }
2092 }
2093 fn snapshot_pages(
2094 live: Arc<AtomicUsize>,
2095 peak: Arc<AtomicUsize>,
2096 ) -> impl FnMut(&Path, Option<crate::DirPageCursor>, usize, usize) -> VfResult<DirectoryPage>
2097 {
2098 move |path, cursor, page_size, max_entries| {
2099 let mut snapshot = match cursor {
2100 Some(cursor) => cursor.into_state::<Snapshot>()?,
2101 None => {
2102 let depth = path.components().count();
2103 let entries = (0..129)
2104 .take(max_entries)
2105 .map(|index| {
2106 let path = path.join(format!("entry-{index}"));
2107 let attrs = crate::VfAttrs {
2108 file: VfFile::from_os_path(&path),
2109 ftype: if index == 0 && depth < 5 {
2110 crate::VfType::Directory
2111 } else {
2112 crate::VfType::Regular
2113 },
2114 ..Default::default()
2115 };
2116 DirEntry::new(path, vfsi_core::metadata_from_attrs(attrs))
2117 })
2118 .collect::<Vec<_>>();
2119 peak.fetch_max(live.fetch_add(1, Ordering::SeqCst) + 1, Ordering::SeqCst);
2120 Snapshot {
2121 remaining: entries.into_iter(),
2122 live: Arc::clone(&live),
2123 }
2124 }
2125 };
2126 let entries = snapshot.remaining.by_ref().take(page_size).collect();
2127 let next = if snapshot.remaining.len() == 0 {
2128 None
2129 } else {
2130 Some(crate::DirPageCursor::new(snapshot))
2131 };
2132 Ok((entries, next))
2133 }
2134 }
2135 #[test]
2136 fn traversal_never_retains_multiple_fallback_snapshots() {
2137 let live = Arc::new(AtomicUsize::new(0));
2138 let peak = Arc::new(AtomicUsize::new(0));
2139 let mut seen = 0;
2140 let completion = visit_walk_pages(
2141 Path::new("/tree"),
2142 crate::WalkOptions::new().max_entries(1000),
2143 snapshot_pages(Arc::clone(&live), Arc::clone(&peak)),
2144 |_| {
2145 seen += 1;
2146 Ok(std::ops::ControlFlow::Continue(()))
2147 },
2148 )
2149 .unwrap();
2150 assert_eq!(completion, TraversalCompletion::Complete);
2151 assert_eq!(seen, 4 * 129);
2152 assert_eq!(live.load(Ordering::SeqCst), 0);
2153 assert_eq!(peak.load(Ordering::SeqCst), 1);
2154 }
2155 #[test]
2156 fn traversal_drops_fallback_snapshot_on_stop_error_and_limit() {
2157 for outcome in 0..3 {
2158 let live = Arc::new(AtomicUsize::new(0));
2159 let peak = Arc::new(AtomicUsize::new(0));
2160 let result = visit_walk_pages(
2161 Path::new("/tree"),
2162 crate::WalkOptions::new().max_entries(if outcome == 2 { 2 } else { 1000 }),
2163 snapshot_pages(Arc::clone(&live), peak),
2164 |_| match outcome {
2165 0 => Ok(std::ops::ControlFlow::Break(())),
2166 1 => Err(VfError::client(0, libc::EIO as u32)),
2167 _ => Ok(std::ops::ControlFlow::Continue(())),
2168 },
2169 );
2170 match outcome {
2171 0 => assert_eq!(result.unwrap(), TraversalCompletion::Stopped),
2172 1 => assert_eq!(result.unwrap_err().err_no(), libc::EIO as u32),
2173 _ => assert_eq!(result.unwrap_err().err_no(), libc::EFBIG as u32),
2174 }
2175 assert_eq!(live.load(Ordering::SeqCst), 0);
2176 }
2177 }
2178}