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 with_indexed_context<'a>(
23 error: VfError,
24 operation: &'static str,
25 path: impl FnOnce(usize) -> Option<&'a Path>,
26) -> VfError {
27 match error.index().and_then(path) {
28 Some(path) => error.with_context(operation, path),
29 None => error,
30 }
31}
32
33fn read_result(result: ReadResult) -> FsReadResult {
34 FsReadResult {
35 offset: result.offset,
36 data: result.data,
37 eof: result.eof,
38 }
39}
40
41fn write_result(result: WriteResult) -> FsWriteResult {
42 FsWriteResult {
43 offset: result.offset,
44 bytes_written: result.bytes_written,
45 stable: result.stable,
46 }
47}
48
49fn poisoned() -> VfError {
50 VfError::client(0, crate::ERR_IO)
51}
52
53fn wrong_result_count(operation: &str, expected: usize, actual: usize) -> VfError {
54 VfError::transport_with_kind(
55 None,
56 crate::TransportKind::InvalidReply,
57 format!("{operation} backend returned {actual} results for {expected} requests"),
58 )
59}
60
61pub struct FsClient<F: HandleBackend> {
68 inner: Arc<SharedBackend<F>>,
69 limits: ResourceLimits,
70}
71
72impl<F: HandleBackend> Clone for FsClient<F> {
73 fn clone(&self) -> Self {
74 Self {
75 inner: Arc::clone(&self.inner),
76 limits: self.limits,
77 }
78 }
79}
80
81impl<F: HandleBackend> fmt::Debug for FsClient<F> {
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 formatter.debug_struct("FsClient").finish_non_exhaustive()
84 }
85}
86
87impl<F: HandleBackend> FsClient<F> {
88 pub fn new(filesystem: F) -> Self {
89 Self {
90 inner: Arc::new(SharedBackend::new(filesystem)),
91 limits: ResourceLimits::default(),
92 }
93 }
94
95 pub fn with_limits(mut self, limits: ResourceLimits) -> Self {
98 self.limits = limits;
99 self
100 }
101
102 pub fn limits(&self) -> ResourceLimits {
103 self.limits
104 }
105
106 fn lock(&self) -> VfResult<BackendGuard<'_, F>> {
107 self.inner.lock().map_err(|_| poisoned())
108 }
109
110 pub fn into_inner(self) -> Result<F, Self> {
111 if self.drain_cleanup().is_err() {
112 return Err(self);
113 }
114 let limits = self.limits;
115 match Arc::try_unwrap(self.inner) {
116 Ok(shared) => shared.into_backend().map_err(|shared| Self {
117 inner: Arc::new(shared),
118 limits,
119 }),
120 Err(inner) => Err(Self { inner, limits }),
121 }
122 }
123
124 pub fn drain_cleanup(&self) -> VfResult<()> {
127 self.inner
128 .lock_without_cleanup()
129 .map_err(|_| poisoned())?
130 .drain_cleanup()
131 }
132}
133
134impl<F: HandleBackend> FsClient<F> {
135 pub(crate) fn capabilities(&self) -> VfResult<Capabilities> {
136 Ok(self.lock()?.capabilities())
137 }
138 pub(crate) fn open(&self, path: impl AsRef<Path>) -> VfResult<OpenFile<F>> {
140 self.open_with_native(OpenOp::new(path.as_ref(), OpenFlags::READ))
141 }
142
143 pub(crate) fn open_with_native(&self, request: OpenOp) -> VfResult<OpenFile<F>> {
144 vfsi_core::internal::validate_open_requests(std::slice::from_ref(&request))?;
145 let file = self.lock()?.open_impl(&request)?;
146 Ok(OpenFile {
147 inner: Arc::clone(&self.inner),
148 file: Some(file),
149 append: request.flags().contains(OpenFlags::APPEND),
150 path: request.into_path(),
151 })
152 }
153}
154
155impl<F: VectorBackend> FsClient<F> {
156 pub(crate) fn vopen(&self, requests: &[OpenOp]) -> VfResult<Vec<OpenFile<F>>> {
162 vfsi_core::internal::validate_open_requests(requests)?;
163 let mut filesystem = self.lock()?;
164 let files = filesystem.vopen_impl(requests).map_err(|error| {
165 with_indexed_context(error, "vopen", |index| {
166 requests.get(index).map(|request| request.path())
167 })
168 })?;
169 if files.len() != requests.len() {
170 let error = wrong_result_count("vopen", requests.len(), files.len());
171 for (index, file) in files.into_iter().enumerate() {
172 if filesystem.close_impl(&file).is_err() {
173 drop(OpenFile {
176 inner: Arc::clone(&self.inner),
177 file: Some(file),
178 append: false,
179 path: requests
180 .get(index)
181 .map(|request| request.path().to_path_buf())
182 .unwrap_or_default(),
183 });
184 }
185 }
186 return Err(error);
187 }
188 drop(filesystem);
189 Ok(files
190 .into_iter()
191 .zip(requests)
192 .map(|(file, request)| OpenFile {
193 inner: Arc::clone(&self.inner),
194 file: Some(file),
195 append: request.flags().contains(OpenFlags::APPEND),
196 path: request.path().to_path_buf(),
197 })
198 .collect())
199 }
200
201 #[doc(hidden)]
205 pub fn vclose<'a>(&self, files: impl IntoIterator<Item = &'a mut OpenFile<F>>) -> VfResult<()>
206 where
207 F: 'a,
208 {
209 let mut files: Vec<_> = files.into_iter().collect();
210 for (index, file) in files.iter().enumerate() {
211 self.validate_owner(file, index)?;
212 }
213 let positions: Vec<_> = files
214 .iter()
215 .enumerate()
216 .filter_map(|(index, file)| (!file.is_closed()).then_some(index))
217 .collect();
218 files.retain(|file| !file.is_closed());
219 if files.is_empty() {
220 return Ok(());
221 }
222 let descriptors: Vec<FileOperand> = files
223 .iter()
224 .map(|file| file.raw().cloned())
225 .collect::<VfResult<_>>()?;
226 self.lock()?.vclose_impl(&descriptors).map_err(|error| {
227 with_indexed_context(error, "vclose_owned", |index| {
228 files.get(index).map(|file| file.path())
229 })
230 .map_index(|index| positions.get(index).copied().unwrap_or(index))
231 })?;
232 for file in &mut files {
233 file.file = None;
234 }
235 Ok(())
236 }
237
238 #[doc(hidden)]
244 pub fn vclose_owned(&self, mut files: Vec<OpenFile<F>>) -> VfResult<()> {
245 self.vclose(&mut files)
246 }
247
248 fn validate_owner(&self, file: &OpenFile<F>, index: usize) -> VfResult<()> {
249 if Arc::ptr_eq(&self.inner, &file.inner) {
250 Ok(())
251 } else {
252 Err(VfError::client(index, crate::ERR_INVAL))
253 }
254 }
255}
256
257fn link_error(error: VfError, paths: &[&Path], operation: &'static str) -> VfError {
258 match error.index() {
259 Some(index) if index < paths.len() => error.with_context(operation, paths[index]),
260 Some(_) => VfError::transport(None, "link backend returned an invalid error index"),
261 None => error,
262 }
263}
264
265mod shared;
266use shared::{BackendGuard, CleanupTarget, SharedBackend};
267mod handles;
268pub use handles::{FsDir, FsRead, FsReadInto, FsWrite, OpenFile};
269mod directory;
270mod metadata;
271mod namespace;
272mod read;
273mod write;