Skip to main content

vfsi_sync/client/
handles.rs

1//! Owned file and directory lifetimes and borrowed I/O requests.
2
3use super::*;
4
5/// Owned, handle-rooted directory. Dropping it queues backend cleanup;
6/// [`close`](Self::close) reports cleanup errors explicitly.
7pub struct FsDir<F: VectorBackend> {
8    pub(super) inner: Arc<SharedBackend<F>>,
9    pub(super) dir: Option<VfDir>,
10    pub(super) path: PathBuf,
11}
12
13impl<F: VectorBackend> fmt::Debug for FsDir<F> {
14    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
15        formatter
16            .debug_struct("FsDir")
17            .field("path", &self.path)
18            .finish_non_exhaustive()
19    }
20}
21
22impl<F: VectorBackend> FsDir<F> {
23    pub fn is_closed(&self) -> bool {
24        self.dir.is_none()
25    }
26    /// Name used at open, retained for diagnostics; not updated after rename.
27    pub fn path(&self) -> &Path {
28        &self.path
29    }
30
31    pub fn close(mut self) -> VfResult<()> {
32        self.try_close()
33    }
34
35    /// Keep cleanup ownership on failure so the caller can retry explicitly.
36    pub fn try_close(&mut self) -> VfResult<()> {
37        let Some(dir) = self.dir.as_ref() else {
38            return Ok(());
39        };
40        self.inner
41            .lock()
42            .map_err(|_| poisoned())?
43            .close_dir_impl(dir)?;
44        self.dir = None;
45        Ok(())
46    }
47}
48
49impl<F: VectorBackend> Drop for FsDir<F> {
50    fn drop(&mut self) {
51        if let Some(dir) = self.dir.take() {
52            self.inner.defer(
53                CleanupTarget::Directory(dir, std::mem::take(&mut self.path)),
54                |backend, target| {
55                    let CleanupTarget::Directory(dir, path) = target else {
56                        unreachable!()
57                    };
58                    backend
59                        .close_dir_impl(dir)
60                        .map_err(|error| error.with_context("close_dir", path))
61                },
62            );
63        }
64    }
65}
66
67/// Owned File Descriptor handle to one open instance, implementing [`crate::FileHandle`].
68///
69/// This RAII value does not borrow the client. It owns the backend handle and
70/// cleanup obligation; borrowed requests keep that instance alive.
71///
72/// File operations use the owning client's `Vfsi` implementation. Dropping an
73/// open file queues cleanup without waiting for the backend lock. Dropping the
74/// final connection owner can perform synchronous teardown. Use `try_close`
75/// when the close result matters.
76pub struct OpenFile<F: HandleBackend> {
77    pub(super) inner: Arc<SharedBackend<F>>,
78    pub(super) file: Option<FileOperand>,
79    // Retained open policy; never exposed through the portable handle API.
80    pub(super) append: bool,
81    pub(super) path: PathBuf,
82}
83
84impl<F: HandleBackend> fmt::Debug for OpenFile<F> {
85    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86        formatter
87            .debug_struct("OpenFile")
88            .field("path", &self.path)
89            .field("is_open", &self.file.is_some())
90            .finish()
91    }
92}
93
94impl<F: HandleBackend> OpenFile<F> {
95    /// Whether explicit close has completed successfully on this handle.
96    pub fn is_closed(&self) -> bool {
97        self.file.is_none()
98    }
99    pub(super) fn raw(&self) -> VfResult<&FileOperand> {
100        self.file
101            .as_ref()
102            .ok_or_else(|| VfError::client(0, crate::ERR_EBADF))
103    }
104
105    pub fn read_request_at(&self, offset: u64, length: usize) -> FsRead<'_, F> {
106        FsRead {
107            file: self,
108            offset: VfOffset::At(offset),
109            length,
110        }
111    }
112
113    /// Name used at open, retained for diagnostics; not a current namespace
114    /// lookup or proof of identity. Handle operations continue to use the
115    /// opened object even if its pathname changes.
116    pub fn path(&self) -> &Path {
117        &self.path
118    }
119
120    pub fn write_request_at<'a>(&'a self, offset: u64, data: &'a [u8]) -> FsWrite<'a, F> {
121        FsWrite::new(self, VfOffset::At(offset), data)
122    }
123
124    pub fn read_request_at_into<'a>(
125        &'a self,
126        offset: u64,
127        buffer: &'a mut [u8],
128    ) -> FsReadInto<'a, F> {
129        FsReadInto {
130            file: self,
131            offset: VfOffset::At(offset),
132            buffer,
133        }
134    }
135
136    /// Attempt to close without consuming the handle. A failed close leaves
137    /// the handle armed so the caller can reconcile or retry cleanup explicitly.
138    /// After an ambiguous close failure, remote state is unknown: do not resume
139    /// data I/O just because `is_closed()` is false. This is local ownership,
140    /// not proof that the server still has the descriptor open.
141    pub fn try_close(&mut self) -> VfResult<()> {
142        let Some(file) = self.file.as_ref() else {
143            return Ok(());
144        };
145        self.inner
146            .lock()
147            .map_err(|_| poisoned())?
148            .close_impl(file)
149            .map_err(|error| error.with_context("close", &self.path))?;
150        self.file = None;
151        Ok(())
152    }
153
154    /// Consume and close the handle. On failure, `Drop` queues cleanup for
155    /// a later operation or explicit cleanup drain; use
156    /// [`try_close`](Self::try_close) to retain control.
157    pub fn close(mut self) -> VfResult<()> {
158        self.try_close()
159    }
160}
161
162/// Typed read request for [`FsClient::vread_native`].
163pub struct FsRead<'a, F: HandleBackend> {
164    pub(super) file: &'a OpenFile<F>,
165    pub(super) offset: VfOffset,
166    pub(super) length: usize,
167}
168
169/// Typed borrowed write request for [`FsClient::vwrite_native`].
170pub type FsWrite<'a, F> =
171    vfsi_core::internal::WriteRequest<&'a OpenFile<F>, &'a [u8], VfOffset, ()>;
172
173/// Typed vector read into caller-provided storage.
174pub struct FsReadInto<'a, F: HandleBackend> {
175    pub(super) file: &'a OpenFile<F>,
176    pub(super) offset: VfOffset,
177    pub(super) buffer: &'a mut [u8],
178}
179
180impl<F: HandleBackend> Drop for OpenFile<F> {
181    fn drop(&mut self) {
182        if let Some(file) = self.file.take() {
183            self.inner.defer(
184                CleanupTarget::File(file, std::mem::take(&mut self.path)),
185                |backend, target| {
186                    let CleanupTarget::File(file, path) = target else {
187                        unreachable!()
188                    };
189                    backend
190                        .close_deferred(file)
191                        .map_err(|error| error.with_context("close", path))
192                },
193            );
194        }
195    }
196}