1use crate::error::ZeroFsError;
7use crate::file::File;
8use std::future::Future;
9use std::io::{self, SeekFrom};
10use std::pin::Pin;
11use std::sync::Arc;
12use std::task::{Context, Poll, ready};
13use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
14
15#[cfg(not(target_arch = "wasm32"))]
16type BoxFut<T> = Pin<Box<dyn Future<Output = T> + Send>>;
17#[cfg(target_arch = "wasm32")]
18type BoxFut<T> = Pin<Box<dyn Future<Output = T>>>;
19
20enum State {
21 Idle,
22 Reading(BoxFut<Result<bytes::Bytes, ZeroFsError>>),
23 Writing {
25 fut: BoxFut<Result<(), ZeroFsError>>,
26 len: u64,
27 },
28 SeekingEnd {
30 fut: BoxFut<Result<u64, ZeroFsError>>,
31 delta: i64,
32 },
33}
34
35pub struct FileCursor {
44 file: Arc<File>,
45 pos: u64,
46 state: State,
47}
48
49impl FileCursor {
50 pub(crate) fn new(file: Arc<File>) -> Self {
51 Self {
52 file,
53 pos: 0,
54 state: State::Idle,
55 }
56 }
57
58 pub fn position(&self) -> u64 {
60 self.pos
61 }
62}
63
64impl AsyncRead for FileCursor {
65 fn poll_read(
66 mut self: Pin<&mut Self>,
67 cx: &mut Context<'_>,
68 buf: &mut ReadBuf<'_>,
69 ) -> Poll<io::Result<()>> {
70 loop {
71 match &mut self.state {
72 State::Reading(fut) => {
73 let data = ready!(fut.as_mut().poll(cx));
74 self.state = State::Idle;
75 let data = data.map_err(io::Error::from)?;
76 let n = data.len().min(buf.remaining());
77 buf.put_slice(&data[..n]);
78 self.pos += n as u64;
79 return Poll::Ready(Ok(()));
80 }
81 State::Idle => {
82 if buf.remaining() == 0 {
83 return Poll::Ready(Ok(()));
84 }
85 let chunk = self.file.max_read_chunk().max(1) as usize;
88 let len = buf.remaining().min(chunk) as u32;
89 let offset = self.pos;
90 let file = Arc::clone(&self.file);
91 self.state =
92 State::Reading(Box::pin(async move { file.read_at(offset, len).await }));
93 }
94 _ => return Poll::Ready(Err(busy())),
95 }
96 }
97 }
98}
99
100impl AsyncWrite for FileCursor {
101 fn poll_write(
102 mut self: Pin<&mut Self>,
103 cx: &mut Context<'_>,
104 buf: &[u8],
105 ) -> Poll<io::Result<usize>> {
106 loop {
107 match &mut self.state {
108 State::Writing { fut, len } => {
109 let res = ready!(fut.as_mut().poll(cx));
110 let len = *len;
111 self.state = State::Idle;
112 res.map_err(io::Error::from)?;
113 self.pos += len;
114 return Poll::Ready(Ok(len as usize));
115 }
116 State::Idle => {
117 if buf.is_empty() {
118 return Poll::Ready(Ok(0));
119 }
120 let offset = self.pos;
123 let data = buf.to_vec();
124 let len = data.len() as u64;
125 let file = Arc::clone(&self.file);
126 self.state = State::Writing {
127 fut: Box::pin(async move { file.write_at(offset, &data).await }),
128 len,
129 };
130 }
131 _ => return Poll::Ready(Err(busy())),
132 }
133 }
134 }
135
136 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
137 match &mut self.state {
141 State::Writing { fut, len } => {
142 let res = ready!(fut.as_mut().poll(cx));
143 let len = *len;
144 self.state = State::Idle;
145 res.map_err(io::Error::from)?;
146 self.pos += len;
147 Poll::Ready(Ok(()))
148 }
149 _ => Poll::Ready(Ok(())),
150 }
151 }
152
153 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
154 self.poll_flush(cx)
155 }
156}
157
158impl AsyncSeek for FileCursor {
159 fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
160 if !matches!(self.state, State::Idle) {
161 return Err(busy());
162 }
163 match position {
164 SeekFrom::Start(n) => self.pos = n,
165 SeekFrom::Current(delta) => {
166 self.pos = self
167 .pos
168 .checked_add_signed(delta)
169 .ok_or_else(negative_seek)?;
170 }
171 SeekFrom::End(delta) => {
172 let file = Arc::clone(&self.file);
173 self.state = State::SeekingEnd {
174 fut: Box::pin(async move { file.metadata().await.map(|m| m.size) }),
175 delta,
176 };
177 }
178 }
179 Ok(())
180 }
181
182 fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
183 if let State::SeekingEnd { fut, delta } = &mut self.state {
184 let size = ready!(fut.as_mut().poll(cx));
185 let delta = *delta;
186 self.state = State::Idle;
187 let size = size.map_err(io::Error::from)?;
188 self.pos = size.checked_add_signed(delta).ok_or_else(negative_seek)?;
189 }
190 Poll::Ready(Ok(self.pos))
191 }
192}
193
194fn busy() -> io::Error {
195 io::Error::other("zerofs FileCursor: another I/O operation is already in flight")
196}
197
198fn negative_seek() -> io::Error {
199 io::Error::new(
200 io::ErrorKind::InvalidInput,
201 "zerofs FileCursor: seek to a negative or out-of-range offset",
202 )
203}