1use std::{io, marker, mem, os::fd, path, rc};
2
3use crate::{
4 backend,
5 driver::route::{self, kind},
6 io::transfer,
7 platform,
8};
9
10pub(crate) mod raw;
11
12pub struct RawMetadata {
13 pub len: u64,
14 pub modified: Option<(i64, u32)>,
15 pub regular: bool,
16}
17
18pub struct Directory {
19 inner: rc::Rc<fd::OwnedFd>,
20 _thread: o3::ThreadBound,
21}
22
23impl Directory {
24 pub fn open(path: impl AsRef<path::Path>) -> io::Result<Self> {
25 let directory = <backend::Backend as platform::Filesystem>::open_directory(path.as_ref())?;
26 Ok(Self {
27 inner: rc::Rc::new(directory),
28 _thread: o3::ThreadBound::NEW,
29 })
30 }
31
32 pub fn relative(&self, path: &str) -> io::Result<OpenPath> {
33 validate_relative(path)?;
34 let path = c_path(path)?;
35 Ok(OpenPath {
36 path,
37 directory: rc::Rc::clone(&self.inner),
38 })
39 }
40}
41
42pub struct OpenPath {
43 pub(crate) path: std::ffi::CString,
44 pub(crate) directory: rc::Rc<fd::OwnedFd>,
45}
46
47pub struct Native(marker::PhantomData<fn()>);
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum Sync {
52 Data,
54 All,
56}
57
58#[allow(private_bounds)]
59pub trait Mode: raw::Mode {}
60
61impl<F: raw::Mode> Mode for F {}
62
63#[repr(transparent)]
64pub struct Metadata<F: Mode> {
65 raw: <F as raw::Mode>::Metadata,
66 mode: marker::PhantomData<fn() -> F>,
67}
68
69pub struct Submission<'a, 'd, F, Tag>
104where
105 F: Mode,
106 Tag: route::Tag,
107{
108 pub(in crate::io::fs) raw: <F as raw::Mode>::Raw,
109 pub(in crate::io::fs) target: route::Operation<'d, Tag>,
110 borrow: marker::PhantomData<&'a mut ()>,
111 mode: marker::PhantomData<fn() -> F>,
112}
113
114pub(crate) struct OpenState<F: Mode> {
115 path: OpenPath,
116 beneath: <F as raw::Mode>::Beneath,
117 mode: marker::PhantomData<fn() -> F>,
118}
119
120impl<F: Mode> Metadata<F> {
121 #[doc(hidden)]
122 pub fn parse(self) -> io::Result<RawMetadata> {
123 let metadata = <F as raw::Mode>::parse(&self.raw)?;
124 if metadata
125 .modified
126 .is_some_and(|(_, nanos)| nanos >= 1_000_000_000)
127 {
128 return Err(io::Error::new(
129 io::ErrorKind::InvalidData,
130 "dope: stat returned an invalid modification nanosecond",
131 ));
132 }
133 Ok(metadata)
134 }
135}
136
137impl<'a, 'd, F, Tag> Submission<'a, 'd, F, Tag>
138where
139 F: Mode,
140 Tag: route::Tag,
141{
142 fn bind(raw: <F as raw::Mode>::Raw, target: route::Operation<'d, Tag>) -> Self {
143 Self {
144 raw,
145 target,
146 borrow: marker::PhantomData,
147 mode: marker::PhantomData,
148 }
149 }
150
151 pub fn read_uninit(
152 fd: fd::BorrowedFd<'a>,
153 buffer: &'a mut [mem::MaybeUninit<u8>],
154 offset: u64,
155 target: route::Target<'d, Tag>,
156 ) -> io::Result<Self> {
157 let len = transfer::Len::try_io(buffer.len())?;
158 Ok(Self::bind(
159 <F as raw::Mode>::read(fd, buffer, len, offset),
160 target.operation(kind::READ),
161 ))
162 }
163
164 pub fn read(
165 fd: fd::BorrowedFd<'a>,
166 buffer: &'a mut [u8],
167 offset: u64,
168 target: route::Target<'d, Tag>,
169 ) -> io::Result<Self> {
170 let len = transfer::Len::try_io(buffer.len())?;
171 Ok(Self::bind(
172 <F as raw::Mode>::read_initialized(fd, buffer, len, offset),
173 target.operation(kind::READ),
174 ))
175 }
176
177 #[doc(hidden)]
178 pub fn write(
179 fd: fd::BorrowedFd<'a>,
180 buffer: &'a [u8],
181 offset: u64,
182 target: route::Target<'d, Tag>,
183 ) -> io::Result<Self> {
184 let len = transfer::Len::try_io(buffer.len())?;
185 Ok(Self::bind(
186 <F as raw::Mode>::write(fd, buffer, len, offset),
187 target.operation(kind::WRITE),
188 ))
189 }
190
191 pub fn stat_fd(
192 fd: fd::BorrowedFd<'a>,
193 output: &'a mut mem::MaybeUninit<Metadata<F>>,
194 target: route::Target<'d, Tag>,
195 ) -> Self {
196 Self::bind(
197 <F as raw::Mode>::stat(fd, output),
198 target.operation(kind::STAT),
199 )
200 }
201
202 pub fn sync(fd: fd::BorrowedFd<'a>, mode: Sync, target: route::Target<'d, Tag>) -> Self {
204 Self::bind(
205 <F as raw::Mode>::sync(fd, mode),
206 target.operation(kind::SYNC),
207 )
208 }
209}
210
211impl<F: Mode> OpenState<F> {
212 pub(crate) fn new(path: OpenPath) -> Self {
213 Self {
214 path,
215 beneath: <F as raw::Mode>::confined_regular_open(),
216 mode: marker::PhantomData,
217 }
218 }
219
220 pub(crate) fn submission<'d, Tag: route::Tag>(
221 &self,
222 target: route::Target<'d, Tag>,
223 ) -> Submission<'_, 'd, F, Tag> {
224 Submission::bind(
225 <F as raw::Mode>::open(
226 &self.beneath,
227 fd::AsFd::as_fd(self.path.directory.as_ref()),
228 self.path.path.as_c_str(),
229 ),
230 target.operation(kind::OPEN),
231 )
232 }
233}
234
235#[doc(hidden)]
236pub struct OpenRequest<F: Mode> {
237 state: OpenState<F>,
238}
239
240impl<F: Mode> OpenRequest<F> {
241 pub fn submission<'d, Tag: route::Tag>(
242 &self,
243 target: route::Target<'d, Tag>,
244 ) -> Submission<'_, 'd, F, Tag> {
245 self.state.submission(target)
246 }
247}
248
249impl OpenPath {
250 #[doc(hidden)]
251 pub fn regular_request<F: Mode>(self) -> OpenRequest<F> {
252 OpenRequest {
253 state: OpenState::new(self),
254 }
255 }
256}
257
258fn c_path(path: &str) -> io::Result<std::ffi::CString> {
259 std::ffi::CString::new(path)
260 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "dope: path has interior nul"))
261}
262
263fn validate_relative(path: &str) -> io::Result<()> {
264 const COMPONENT_MAX: usize = 255;
265
266 let invalid = || {
267 io::Error::new(
268 io::ErrorKind::InvalidInput,
269 "dope: path is not a confined relative path",
270 )
271 };
272
273 if path.is_empty() || path.starts_with('/') {
274 return Err(invalid());
275 }
276
277 let mut meaningful = false;
278 let mut oversized_component = false;
279 for component in path.split('/') {
280 if component == ".." {
281 return Err(invalid());
282 }
283 meaningful |= !component.is_empty() && component != ".";
284 oversized_component |= component.len() > COMPONENT_MAX;
285 }
286
287 if !meaningful {
288 return Err(invalid());
289 }
290 if path.len() >= libc::PATH_MAX as usize || oversized_component {
291 return Err(io::Error::new(
292 io::ErrorKind::InvalidInput,
293 "dope: confined path exceeds the platform limit",
294 ));
295 }
296 Ok(())
297}