1use std::collections::{HashMap, HashSet};
4use std::fmt;
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8use vfsi_core::api::internal::OwnedReadResult as FsReadResult;
9use vfsi_core::api::{
10 DirectoryListing, ReadIntoResult as FsReadIntoResult, ResourceLimits, StreamCompletion,
11 WriteResult as FsWriteResult,
12};
13
14use crate::backend::{HandleBackend, VectorBackend};
15use crate::traits::{validate_read_into_results, validate_read_results, validate_write_results};
16use crate::{
17 AttrMask, Attrs, Capabilities, DirEntry, FileOperand, OpenFlags, OpenOp, ReadAllOptions,
18 ReadOp, ReadResult, RemoveOptions, StreamOptions, VfDir, VfError, VfOffset, VfResult, WriteOp,
19 WriteResult,
20};
21
22fn read_result(result: ReadResult) -> FsReadResult {
23 FsReadResult {
24 offset: result.offset,
25 data: result.data,
26 eof: result.eof,
27 }
28}
29
30fn write_result(result: WriteResult) -> FsWriteResult {
31 FsWriteResult {
32 offset: result.offset,
33 bytes_written: result.bytes_written,
34 stable: result.stable,
35 }
36}
37
38fn poisoned() -> VfError {
39 VfError::client(0, crate::ERR_IO)
40}
41
42fn wrong_result_count(operation: &str, expected: usize, actual: usize) -> VfError {
43 VfError::transport_with_kind(
44 None,
45 crate::TransportKind::InvalidReply,
46 format!("{operation} backend returned {actual} results for {expected} requests"),
47 )
48}
49
50pub struct FsClient<F: HandleBackend> {
57 inner: Arc<SharedBackend<F>>,
58 limits: ResourceLimits,
59}
60
61impl<F: HandleBackend> Clone for FsClient<F> {
62 fn clone(&self) -> Self {
63 Self {
64 inner: Arc::clone(&self.inner),
65 limits: self.limits,
66 }
67 }
68}
69
70impl<F: HandleBackend> fmt::Debug for FsClient<F> {
71 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72 formatter.debug_struct("FsClient").finish_non_exhaustive()
73 }
74}
75
76impl<F: HandleBackend> FsClient<F> {
77 pub fn new(filesystem: F) -> Self {
78 Self {
79 inner: Arc::new(SharedBackend::new(filesystem)),
80 limits: ResourceLimits::default(),
81 }
82 }
83
84 pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
87 self.limits = limits;
88 self
89 }
90
91 pub fn limits(&self) -> ResourceLimits {
92 self.limits
93 }
94
95 fn lock(&self) -> VfResult<BackendGuard<'_, F>> {
96 self.inner.lock().map_err(|_| poisoned())
97 }
98
99 pub fn into_inner(self) -> Result<F, Self> {
100 if self.drain_cleanup().is_err() {
101 return Err(self);
102 }
103 let limits = self.limits;
104 match Arc::try_unwrap(self.inner) {
105 Ok(shared) => shared.into_backend().map_err(|shared| Self {
106 inner: Arc::new(shared),
107 limits,
108 }),
109 Err(inner) => Err(Self { inner, limits }),
110 }
111 }
112
113 pub fn drain_cleanup(&self) -> VfResult<()> {
116 self.inner
117 .lock_without_cleanup()
118 .map_err(|_| poisoned())?
119 .drain_cleanup()
120 }
121}
122
123impl<F: HandleBackend> FsClient<F> {
124 pub(crate) fn capabilities(&self) -> VfResult<Capabilities> {
125 Ok(self.lock()?.capabilities())
126 }
127 pub(crate) fn open(&self, path: impl AsRef<Path>) -> VfResult<OpenFile<F>> {
129 self.open_with_native(OpenOp::new(path.as_ref(), OpenFlags::READ))
130 }
131
132 pub(crate) fn open_with_native(&self, request: OpenOp) -> VfResult<OpenFile<F>> {
133 vfsi_core::internal::validate_open_requests(std::slice::from_ref(&request))?;
134 let file = self.lock()?.open_impl(&request)?;
135 Ok(OpenFile {
136 inner: Arc::clone(&self.inner),
137 file: Some(file),
138 append: request.flags().contains(OpenFlags::APPEND),
139 path: request.into_path(),
140 })
141 }
142}
143
144impl<F: VectorBackend> FsClient<F> {
145 pub(crate) fn vopen(&self, requests: &[OpenOp]) -> VfResult<Vec<OpenFile<F>>> {
151 vfsi_core::internal::validate_open_requests(requests)?;
152 let mut filesystem = self.lock()?;
153 let files = filesystem.vopen_impl(requests).map_err(|error| {
154 error
155 .index()
156 .and_then(|index| requests.get(index))
157 .map_or(error.clone(), |request| {
158 error.with_context("vopen", request.path())
159 })
160 })?;
161 if files.len() != requests.len() {
162 let error = wrong_result_count("vopen", requests.len(), files.len());
163 for file in &files {
164 let _ = filesystem.close_impl(file);
165 }
166 return Err(error);
167 }
168 drop(filesystem);
169 Ok(files
170 .into_iter()
171 .zip(requests)
172 .map(|(file, request)| OpenFile {
173 inner: Arc::clone(&self.inner),
174 file: Some(file),
175 append: request.flags().contains(OpenFlags::APPEND),
176 path: request.path().to_path_buf(),
177 })
178 .collect())
179 }
180
181 #[doc(hidden)]
185 pub fn vclose<'a>(&self, files: impl IntoIterator<Item = &'a mut OpenFile<F>>) -> VfResult<()>
186 where
187 F: 'a,
188 {
189 let mut files: Vec<_> = files.into_iter().collect();
190 for (index, file) in files.iter().enumerate() {
191 self.validate_owner(file, index)?;
192 }
193 let positions: Vec<_> = files
194 .iter()
195 .enumerate()
196 .filter_map(|(index, file)| (!file.is_closed()).then_some(index))
197 .collect();
198 files.retain(|file| !file.is_closed());
199 if files.is_empty() {
200 return Ok(());
201 }
202 let descriptors: Vec<FileOperand> = files
203 .iter()
204 .map(|file| file.raw().cloned())
205 .collect::<VfResult<_>>()?;
206 self.lock()?.vclose_impl(&descriptors).map_err(|error| {
207 error
208 .index()
209 .and_then(|index| files.get(index))
210 .map_or(error.clone(), |file| {
211 error.with_context("vclose_owned", file.path())
212 })
213 .map_index(|index| positions.get(index).copied().unwrap_or(index))
214 })?;
215 for file in &mut files {
216 file.file = None;
217 }
218 Ok(())
219 }
220
221 #[doc(hidden)]
227 pub fn vclose_owned(&self, mut files: Vec<OpenFile<F>>) -> VfResult<()> {
228 self.vclose(&mut files)
229 }
230
231 fn validate_owner(&self, file: &OpenFile<F>, index: usize) -> VfResult<()> {
232 if Arc::ptr_eq(&self.inner, &file.inner) {
233 Ok(())
234 } else {
235 Err(VfError::client(index, crate::ERR_INVAL))
236 }
237 }
238}
239
240fn link_error(error: VfError, paths: &[&Path], operation: &'static str) -> VfError {
241 match error.index() {
242 Some(index) if index < paths.len() => error.with_context(operation, paths[index]),
243 Some(_) => VfError::transport(None, "link backend returned an invalid error index"),
244 None => error,
245 }
246}
247
248mod shared;
249use shared::{BackendGuard, CleanupTarget, SharedBackend};
250mod handles;
251pub use handles::{FsDir, FsRead, FsReadInto, FsWrite, OpenFile};
252mod directory;
253mod metadata;
254mod namespace;
255mod read;
256mod write;