1use crate::TrappableError;
2use crate::filesystem::File;
3use crate::filesystem::sys;
4use crate::p2::bindings::filesystem::types;
5use crate::p2::{InputStream, OutputStream, Pollable, StreamError, StreamResult};
6use crate::runtime::AbortOnDropJoinHandle;
7use bytes::{Bytes, BytesMut};
8use std::io;
9use std::mem;
10use wasmtime::format_err;
11
12pub type FsResult<T> = Result<T, FsError>;
13
14pub type FsError = TrappableError<types::ErrorCode>;
15
16impl From<crate::filesystem::ErrorCode> for types::ErrorCode {
17 fn from(error: crate::filesystem::ErrorCode) -> Self {
18 match error {
19 crate::filesystem::ErrorCode::Access => Self::Access,
20 crate::filesystem::ErrorCode::Already => Self::Already,
21 crate::filesystem::ErrorCode::BadDescriptor => Self::BadDescriptor,
22 crate::filesystem::ErrorCode::Busy => Self::Busy,
23 crate::filesystem::ErrorCode::Exist => Self::Exist,
24 crate::filesystem::ErrorCode::FileTooLarge => Self::FileTooLarge,
25 crate::filesystem::ErrorCode::IllegalByteSequence => Self::IllegalByteSequence,
26 crate::filesystem::ErrorCode::InProgress => Self::InProgress,
27 crate::filesystem::ErrorCode::Interrupted => Self::Interrupted,
28 crate::filesystem::ErrorCode::Invalid => Self::Invalid,
29 crate::filesystem::ErrorCode::Io => Self::Io,
30 crate::filesystem::ErrorCode::IsDirectory => Self::IsDirectory,
31 crate::filesystem::ErrorCode::Loop => Self::Loop,
32 crate::filesystem::ErrorCode::TooManyLinks => Self::TooManyLinks,
33 crate::filesystem::ErrorCode::NameTooLong => Self::NameTooLong,
34 crate::filesystem::ErrorCode::NoEntry => Self::NoEntry,
35 crate::filesystem::ErrorCode::InsufficientMemory => Self::InsufficientMemory,
36 crate::filesystem::ErrorCode::InsufficientSpace => Self::InsufficientSpace,
37 crate::filesystem::ErrorCode::NotDirectory => Self::NotDirectory,
38 crate::filesystem::ErrorCode::NotEmpty => Self::NotEmpty,
39 crate::filesystem::ErrorCode::Unsupported => Self::Unsupported,
40 crate::filesystem::ErrorCode::Overflow => Self::Overflow,
41 crate::filesystem::ErrorCode::NotPermitted => Self::NotPermitted,
42 crate::filesystem::ErrorCode::Pipe => Self::Pipe,
43 crate::filesystem::ErrorCode::InvalidSeek => Self::InvalidSeek,
44 }
45 }
46}
47
48impl From<crate::filesystem::ErrorCode> for FsError {
49 fn from(error: crate::filesystem::ErrorCode) -> Self {
50 types::ErrorCode::from(error).into()
51 }
52}
53
54impl From<wasmtime::component::ResourceTableError> for FsError {
55 fn from(error: wasmtime::component::ResourceTableError) -> Self {
56 Self::trap(error)
57 }
58}
59
60impl From<io::Error> for FsError {
61 fn from(error: io::Error) -> Self {
62 types::ErrorCode::from(error).into()
63 }
64}
65
66pub struct FileInputStream {
67 file: File,
68 position: u64,
69 state: ReadState,
70 max_read_size: usize,
71}
72enum ReadState {
73 Idle,
74 Waiting(AbortOnDropJoinHandle<ReadState>),
75 DataAvailable(Bytes),
76 Error(io::Error),
77 Closed,
78}
79impl FileInputStream {
80 pub fn new(file: &File, position: u64, max_read_size: usize) -> Self {
81 Self {
82 file: file.clone(),
83 position,
84 state: ReadState::Idle,
85 max_read_size,
86 }
87 }
88
89 fn blocking_read(file: &std::fs::File, offset: u64, size: usize) -> ReadState {
90 let mut buf = BytesMut::zeroed(size);
91 loop {
92 match sys::read_at_cursor_unspecified(file, &mut buf, offset) {
93 Ok(0) => return ReadState::Closed,
94 Ok(n) => {
95 buf.truncate(n);
96 return ReadState::DataAvailable(buf.freeze());
97 }
98 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
99 }
101 Err(e) => return ReadState::Error(e),
102 }
103 }
104 }
105
106 async fn wait_ready(&mut self) {
108 match &mut self.state {
109 ReadState::Waiting(task) => {
110 self.state = task.await;
111 }
112 _ => {}
113 }
114 }
115}
116#[async_trait::async_trait]
117impl InputStream for FileInputStream {
118 fn read(&mut self, size: usize) -> StreamResult<Bytes> {
119 match &mut self.state {
120 ReadState::Idle => {
121 if size == 0 {
122 return Ok(Bytes::new());
123 }
124
125 let p = self.position;
126 let size = size.min(self.max_read_size);
127 self.state = ReadState::Waiting(
128 self.file
129 .spawn_blocking(move |f| Self::blocking_read(f, p, size)),
130 );
131 Ok(Bytes::new())
132 }
133 ReadState::DataAvailable(b) => {
134 let min_len = b.len().min(size);
135 let chunk = b.split_to(min_len);
136 if b.len() == 0 {
137 self.state = ReadState::Idle;
138 }
139 self.position += min_len as u64;
140 Ok(chunk)
141 }
142 ReadState::Waiting(_) => Ok(Bytes::new()),
143 ReadState::Error(_) => match mem::replace(&mut self.state, ReadState::Closed) {
144 ReadState::Error(e) => Err(StreamError::LastOperationFailed(e.into())),
145 _ => unreachable!(),
146 },
147 ReadState::Closed => Err(StreamError::Closed),
148 }
149 }
150 async fn blocking_read(&mut self, size: usize) -> StreamResult<Bytes> {
153 if size == 0 {
154 return self.read(size);
155 }
156 self.wait_ready().await;
157
158 if let ReadState::Idle = self.state {
160 let p = self.position;
161 let size = size.min(self.max_read_size);
162 self.state = self
163 .file
164 .run_blocking(move |f| Self::blocking_read(f, p, size))
165 .await;
166 }
167
168 self.read(size)
169 }
170 async fn cancel(&mut self) {
171 match mem::replace(&mut self.state, ReadState::Closed) {
172 ReadState::Waiting(task) => {
173 task.cancel().await;
184 }
185 _ => {}
186 }
187 }
188}
189#[async_trait::async_trait]
190impl Pollable for FileInputStream {
191 async fn ready(&mut self) {
192 if let ReadState::Idle = self.state {
193 const DEFAULT_READ_SIZE: usize = 4096;
197 let p = self.position;
198 self.state = ReadState::Waiting(
199 self.file
200 .spawn_blocking(move |f| Self::blocking_read(f, p, DEFAULT_READ_SIZE)),
201 );
202 }
203
204 self.wait_ready().await
205 }
206}
207
208#[derive(Clone, Copy)]
209pub(crate) enum FileOutputMode {
210 Position(u64),
211 Append,
212}
213
214pub(crate) struct FileOutputStream {
215 file: File,
216 mode: FileOutputMode,
217 state: OutputState,
218 max_write_size: usize,
219}
220
221enum OutputState {
222 Ready,
223 Waiting(AbortOnDropJoinHandle<io::Result<usize>>),
226 Error(io::Error),
228 Closed,
229}
230
231impl FileOutputStream {
232 pub fn write_at(file: &File, position: u64, max_write_size: usize) -> Self {
233 Self {
234 file: file.clone(),
235 mode: FileOutputMode::Position(position),
236 state: OutputState::Ready,
237 max_write_size,
238 }
239 }
240
241 pub fn append(file: &File, max_write_size: usize) -> Self {
242 Self {
243 file: file.clone(),
244 mode: FileOutputMode::Append,
245 state: OutputState::Ready,
246 max_write_size,
247 }
248 }
249
250 fn blocking_write(
251 file: &std::fs::File,
252 mut buf: Bytes,
253 mode: FileOutputMode,
254 ) -> io::Result<usize> {
255 match mode {
256 FileOutputMode::Position(mut p) => {
257 let mut total = 0;
258 loop {
259 let nwritten = sys::write_at_cursor_unspecified(file, buf.as_ref(), p)?;
260 let _ = buf.split_to(nwritten);
262 p += nwritten as u64;
263 total += nwritten;
264 if buf.is_empty() {
265 break;
266 }
267 }
268 Ok(total)
269 }
270 FileOutputMode::Append => {
271 let mut total = 0;
272 loop {
273 let nwritten = sys::append_cursor_unspecified(file, buf.as_ref())?;
274 let _ = buf.split_to(nwritten);
275 total += nwritten;
276 if buf.is_empty() {
277 break;
278 }
279 }
280 Ok(total)
281 }
282 }
283 }
284}
285
286#[async_trait::async_trait]
287impl OutputStream for FileOutputStream {
288 fn write(&mut self, buf: Bytes) -> Result<(), StreamError> {
289 match self.state {
290 OutputState::Ready if buf.len() <= self.max_write_size => {}
291 OutputState::Ready => {
292 return Err(StreamError::Trap(format_err!("write exceeded budget")));
293 }
294 OutputState::Closed => return Err(StreamError::Closed),
295 OutputState::Waiting(_) if buf.is_empty() => return Ok(()),
296 OutputState::Waiting(_) | OutputState::Error(_) => {
297 return Err(StreamError::Trap(format_err!(
299 "write not permitted: check_write not called first"
300 )));
301 }
302 }
303
304 let m = self.mode;
305 self.state = OutputState::Waiting(
306 self.file
307 .spawn_blocking(move |f| Self::blocking_write(f, buf, m)),
308 );
309 Ok(())
310 }
311 async fn blocking_write_and_flush(&mut self, buf: Bytes) -> StreamResult<()> {
314 self.ready().await;
315
316 match self.state {
317 OutputState::Ready => {}
318 OutputState::Closed => return Err(StreamError::Closed),
319 OutputState::Error(_) => match mem::replace(&mut self.state, OutputState::Closed) {
320 OutputState::Error(e) => return Err(StreamError::LastOperationFailed(e.into())),
321 _ => unreachable!(),
322 },
323 OutputState::Waiting(_) => unreachable!("we've just waited for readiness"),
324 }
325
326 let m = self.mode;
327 match self
328 .file
329 .run_blocking(move |f| Self::blocking_write(f, buf, m))
330 .await
331 {
332 Ok(nwritten) => {
333 if let FileOutputMode::Position(p) = &mut self.mode {
334 *p += nwritten as u64;
335 }
336 self.state = OutputState::Ready;
337 Ok(())
338 }
339 Err(e) => {
340 self.state = OutputState::Closed;
341 Err(StreamError::LastOperationFailed(e.into()))
342 }
343 }
344 }
345 fn flush(&mut self) -> Result<(), StreamError> {
346 match self.state {
347 OutputState::Ready | OutputState::Waiting(_) => Ok(()),
351 OutputState::Closed => Err(StreamError::Closed),
352 OutputState::Error(_) => match mem::replace(&mut self.state, OutputState::Closed) {
353 OutputState::Error(e) => Err(StreamError::LastOperationFailed(e.into())),
354 _ => unreachable!(),
355 },
356 }
357 }
358 fn check_write(&mut self) -> Result<usize, StreamError> {
359 match self.state {
360 OutputState::Ready => Ok(self.max_write_size),
361 OutputState::Closed => Err(StreamError::Closed),
362 OutputState::Error(_) => match mem::replace(&mut self.state, OutputState::Closed) {
363 OutputState::Error(e) => Err(StreamError::LastOperationFailed(e.into())),
364 _ => unreachable!(),
365 },
366 OutputState::Waiting(_) => Ok(0),
367 }
368 }
369 async fn cancel(&mut self) {
370 match mem::replace(&mut self.state, OutputState::Closed) {
371 OutputState::Waiting(task) => {
372 task.cancel().await;
383 }
384 _ => {}
385 }
386 }
387}
388
389#[async_trait::async_trait]
390impl Pollable for FileOutputStream {
391 async fn ready(&mut self) {
392 if let OutputState::Waiting(task) = &mut self.state {
393 self.state = match task.await {
394 Ok(nwritten) => {
395 if let FileOutputMode::Position(p) = &mut self.mode {
396 *p += nwritten as u64;
397 }
398 OutputState::Ready
399 }
400 Err(e) => OutputState::Error(e),
401 };
402 }
403 }
404}
405
406pub struct ReaddirIterator(
407 std::sync::Mutex<Box<dyn Iterator<Item = FsResult<types::DirectoryEntry>> + Send + 'static>>,
408);
409
410impl ReaddirIterator {
411 pub(crate) fn new(
412 i: impl Iterator<Item = FsResult<types::DirectoryEntry>> + Send + 'static,
413 ) -> Self {
414 ReaddirIterator(std::sync::Mutex::new(Box::new(i)))
415 }
416 pub(crate) fn next(&self) -> FsResult<Option<types::DirectoryEntry>> {
417 self.0.lock().unwrap().next().transpose()
418 }
419}
420
421impl IntoIterator for ReaddirIterator {
422 type Item = FsResult<types::DirectoryEntry>;
423 type IntoIter = Box<dyn Iterator<Item = Self::Item> + Send>;
424
425 fn into_iter(self) -> Self::IntoIter {
426 self.0.into_inner().unwrap()
427 }
428}