Skip to main content

dolang_vfs/file/
cursor.rs

1//! The cursor-based I/O traits of [`File`], implemented once over operations
2//! each backend starts.
3//!
4//! A backend only knows how to start a read, a write or a size query at an
5//! offset and drive it to completion, and where to store the position, which
6//! every clone of a file shares. Everything else about the cursor lives here:
7//! seek resolution, read-ahead, and the rule that one operation is in flight
8//! at a time.
9//!
10//! Operations are per clone. Clones moving the one cursor at once are not
11//! serialized, as with file descriptors: each operation reads the position
12//! when it starts and stores its own end when it finishes.
13
14use std::{
15    fmt,
16    future::Future,
17    io,
18    mem::{self, MaybeUninit},
19    pin::Pin,
20    sync::atomic::{AtomicU64, Ordering},
21    task::{Context, Poll, ready},
22};
23
24use bytes::BytesMut;
25use futures::io::{AsyncRead, AsyncSeek, AsyncWrite};
26
27use super::{File, FileInner};
28use crate::{client, direct, error::Result};
29
30/// An operation of one backend or the other.
31pub(crate) enum Either<D, C> {
32    Direct(D),
33    Client(C),
34}
35
36impl<T, D, C> Future for Either<D, C>
37where
38    D: Future<Output = T> + Unpin,
39    C: Future<Output = T> + Unpin,
40{
41    type Output = T;
42
43    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
44        match self.get_mut() {
45            Self::Direct(op) => Pin::new(op).poll(cx),
46            Self::Client(op) => Pin::new(op).poll(cx),
47        }
48    }
49}
50
51impl Either<direct::Body, client::Body> {
52    fn poll_read_uninit(
53        &mut self,
54        cx: &mut Context<'_>,
55        buf: &mut [MaybeUninit<u8>],
56    ) -> Poll<io::Result<usize>> {
57        match self {
58            Self::Direct(body) => Poll::Ready(Ok(body.read_uninit(buf))),
59            Self::Client(body) => body.poll_read_uninit(cx, buf),
60        }
61    }
62}
63
64/// A write in flight: bytes are handed over first, then the write completes.
65pub(crate) trait WriteOp {
66    /// Accepts bytes toward the length the write was started with.
67    fn poll_send(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>>;
68    /// Completes the write with the bytes accepted so far, returning how many
69    /// were written and the position just past them.
70    fn poll_finish(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<(usize, u64)>>;
71}
72
73impl<D: WriteOp, C: WriteOp> WriteOp for Either<D, C> {
74    fn poll_send(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
75        match self {
76            Self::Direct(op) => op.poll_send(cx, buf),
77            Self::Client(op) => op.poll_send(cx, buf),
78        }
79    }
80
81    fn poll_finish(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<(usize, u64)>> {
82        match self {
83            Self::Direct(op) => op.poll_finish(cx),
84            Self::Client(op) => op.poll_finish(cx),
85        }
86    }
87}
88
89type ReadOp = Either<direct::ReadOp, client::ReadOp>;
90type Body = Either<direct::Body, client::Body>;
91type Write = Either<direct::WriteOp, client::WriteOp>;
92type SizeOp = Either<direct::SizeOp, client::SizeOp>;
93
94/// One clone's state for the cursor-based traits, allocated on first use.
95///
96/// The position advances only as bytes are delivered, so read-ahead can simply
97/// be dropped on a seek.
98#[derive(Default)]
99pub(super) struct Cursor {
100    op: Op,
101    /// Buffer recycled across reads and writes by backends that copy, so
102    /// streaming does not allocate per call.
103    scratch: BytesMut,
104}
105
106#[derive(Default)]
107enum Op {
108    #[default]
109    Idle,
110    /// A read started at the given offset.
111    Read(ReadOp, u64),
112    /// Bytes read at the position and not yet all delivered.
113    Body {
114        body: Body,
115        /// Offset of the body's next byte. Once the position is anywhere
116        /// else, another clone moved it, and the body is dropped.
117        at: u64,
118        /// Whether any byte has been delivered. A body that ends without
119        /// delivering one means end of file.
120        delivered: bool,
121    },
122    Write {
123        op: Write,
124        target: usize,
125        sent: usize,
126        /// Bytes already reported to the caller as written. The final chunk is
127        /// reported only once the write completes, so a write that fails or
128        /// lands short is never reported as done.
129        reported: usize,
130    },
131    /// An end-relative seek resolving the file's length.
132    Size(SizeOp, i64),
133}
134
135impl fmt::Debug for Cursor {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        let op = match self.op {
138            Op::Idle => "idle",
139            Op::Read(..) => "read",
140            Op::Body { .. } => "body",
141            Op::Write { .. } => "write",
142            Op::Size(..) => "size",
143        };
144        f.debug_struct("Cursor")
145            .field("op", &op)
146            .finish_non_exhaustive()
147    }
148}
149
150impl Cursor {
151    /// Drops unread read-ahead, keeping its buffer.
152    fn drop_body(&mut self) {
153        if !matches!(self.op, Op::Body { .. }) {
154            return;
155        }
156        if let Op::Body {
157            body: Either::Direct(body),
158            ..
159        } = mem::take(&mut self.op)
160        {
161            self.scratch = body.into_scratch();
162        }
163    }
164
165    /// Ends a write, keeping its buffer.
166    fn end_write(&mut self) {
167        if let Op::Write {
168            op: Either::Direct(op),
169            ..
170        } = mem::take(&mut self.op)
171        {
172            self.scratch = op.into_scratch();
173        }
174    }
175
176    fn is_busy(&self) -> bool {
177        !matches!(self.op, Op::Idle | Op::Body { .. })
178    }
179
180    /// Abandons whatever is in flight. The client asks the peer to stop and
181    /// waits for its answer, so that a request following this one does not
182    /// find the file still in use there.
183    async fn cancel(&mut self) {
184        match mem::take(&mut self.op) {
185            Op::Read(Either::Client(op), _) => op.cancel().await,
186            Op::Write {
187                op: Either::Client(op),
188                ..
189            } => op.cancel().await,
190            Op::Size(Either::Client(op), _) => op.cancel().await,
191            _ => {}
192        }
193    }
194}
195
196/// Applies `delta` to `base`, rejecting a negative result as `lseek` does.
197fn offset_delta(base: u64, delta: i64) -> io::Result<u64> {
198    base.checked_add_signed(delta).ok_or_else(|| {
199        io::Error::new(
200            io::ErrorKind::InvalidInput,
201            "seek would move the cursor outside the representable range",
202        )
203    })
204}
205
206fn in_progress(what: &str) -> io::Error {
207    io::Error::other(format!(
208        "file {what} while another operation is in progress"
209    ))
210}
211
212/// Polls a read to its body. The two backends' bodies differ, so this cannot
213/// be `Either`'s own `Future`.
214fn poll_read_op(op: &mut ReadOp, cx: &mut Context<'_>) -> Poll<io::Result<Body>> {
215    Poll::Ready(match op {
216        Either::Direct(op) => ready!(Pin::new(op).poll(cx)).map(Either::Direct),
217        Either::Client(op) => ready!(Pin::new(op).poll(cx)).map(Either::Client),
218    })
219}
220
221impl FileInner {
222    /// The cursor position, shared by every clone.
223    pub(crate) fn position(&self) -> &AtomicU64 {
224        match self {
225            Self::Direct(file) => file.position(),
226            Self::Client(file) => file.position(),
227        }
228    }
229
230    fn start_read(&self, offset: u64, len: usize, scratch: &mut BytesMut) -> Result<ReadOp> {
231        Ok(match self {
232            Self::Direct(file) => Either::Direct(file.start_read(offset, len, scratch)?),
233            Self::Client(file) => Either::Client(file.start_read(offset, len)?),
234        })
235    }
236
237    fn start_write(
238        &self,
239        offset: u64,
240        len: usize,
241        scratch: &mut BytesMut,
242    ) -> Result<(Write, usize)> {
243        Ok(match self {
244            Self::Direct(file) => {
245                let (op, target) = file.start_write(offset, len, scratch)?;
246                (Either::Direct(op), target)
247            }
248            Self::Client(file) => (Either::Client(file.start_write(offset)?), len),
249        })
250    }
251
252    fn start_size(&self) -> Result<SizeOp> {
253        Ok(match self {
254            Self::Direct(file) => Either::Direct(file.start_size()?),
255            Self::Client(file) => Either::Client(file.start_size()?),
256        })
257    }
258}
259
260impl File {
261    fn parts(&mut self) -> (&FileInner, &mut Cursor) {
262        (&self.inner, self.cursor.get_or_insert_default())
263    }
264
265    /// Fails if a cursor operation is still in flight.
266    pub(super) fn check_idle(&self) -> Result<()> {
267        match &self.cursor {
268            Some(cursor) if cursor.is_busy() => {
269                Err(io::Error::other(format!("file operation {cursor:?} is still pending")).into())
270            }
271            _ => Ok(()),
272        }
273    }
274
275    /// Abandons any cursor operation in flight.
276    pub(super) async fn cancel_cursor(&mut self) {
277        if let Some(cursor) = self.cursor.as_mut() {
278            cursor.cancel().await;
279        }
280    }
281}
282
283impl File {
284    /// Reads at the cursor into uninitialized storage, returning how many
285    /// bytes at its front were filled. Zero means end of file, unless `buf`
286    /// is empty.
287    ///
288    /// This is [`AsyncRead::poll_read`] without the requirement that `buf` be
289    /// initialized, so a caller reading into fresh memory need not zero it
290    /// first. The remote backend writes the reply straight into `buf`.
291    pub fn poll_read_uninit(
292        &mut self,
293        cx: &mut Context<'_>,
294        buf: &mut [MaybeUninit<u8>],
295    ) -> Poll<io::Result<usize>> {
296        if buf.is_empty() {
297            return Poll::Ready(Ok(0));
298        }
299        let (inner, cursor) = self.parts();
300        let pos = inner.position();
301        loop {
302            match &mut cursor.op {
303                Op::Body { at, .. } if pos.load(Ordering::Relaxed) != *at => cursor.drop_body(),
304                Op::Body {
305                    body,
306                    at,
307                    delivered,
308                } => {
309                    let read = match ready!(body.poll_read_uninit(cx, buf)) {
310                        Ok(read) => read,
311                        Err(error) => {
312                            cursor.drop_body();
313                            return Poll::Ready(Err(error));
314                        }
315                    };
316                    if read > 0 {
317                        *delivered = true;
318                        *at += read as u64;
319                        pos.store(*at, Ordering::Relaxed);
320                        return Poll::Ready(Ok(read));
321                    }
322                    let end_of_file = !*delivered;
323                    cursor.drop_body();
324                    if end_of_file {
325                        return Poll::Ready(Ok(0));
326                    }
327                }
328                Op::Read(op, at) => {
329                    let at = *at;
330                    let result = ready!(poll_read_op(op, cx));
331                    cursor.op = Op::Idle;
332                    cursor.op = Op::Body {
333                        body: result?,
334                        at,
335                        delivered: false,
336                    };
337                }
338                Op::Idle => {
339                    let at = pos.load(Ordering::Relaxed);
340                    let op = inner.start_read(at, buf.len(), &mut cursor.scratch)?;
341                    cursor.op = Op::Read(op, at);
342                }
343                _ => return Poll::Ready(Err(in_progress("read polled"))),
344            }
345        }
346    }
347}
348
349impl AsyncRead for File {
350    fn poll_read(
351        self: Pin<&mut Self>,
352        cx: &mut Context<'_>,
353        buf: &mut [u8],
354    ) -> Poll<io::Result<usize>> {
355        // SAFETY: `poll_read_uninit` only writes initialized bytes, so `buf`
356        // stays initialized.
357        let buf = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit<u8>]) };
358        self.get_mut().poll_read_uninit(cx, buf)
359    }
360}
361
362impl AsyncWrite for File {
363    fn poll_write(
364        self: Pin<&mut Self>,
365        cx: &mut Context<'_>,
366        buf: &[u8],
367    ) -> Poll<io::Result<usize>> {
368        if buf.is_empty() {
369            return Poll::Ready(Ok(0));
370        }
371        let (inner, cursor) = self.get_mut().parts();
372        loop {
373            match &mut cursor.op {
374                // A write invalidates whatever was read ahead of it.
375                Op::Body { .. } => cursor.drop_body(),
376                Op::Idle => {
377                    let (op, target) = inner.start_write(
378                        inner.position().load(Ordering::Relaxed),
379                        buf.len(),
380                        &mut cursor.scratch,
381                    )?;
382                    cursor.op = Op::Write {
383                        op,
384                        target,
385                        sent: 0,
386                        reported: 0,
387                    };
388                }
389                Op::Write {
390                    op,
391                    target,
392                    sent,
393                    reported,
394                } => {
395                    if *sent < *target {
396                        let chunk = &buf[..buf.len().min(*target - *sent)];
397                        let sent_now = match ready!(op.poll_send(cx, chunk)) {
398                            Ok(0) => Err(io::ErrorKind::WriteZero.into()),
399                            result => result,
400                        };
401                        let sent_now = match sent_now {
402                            Ok(sent_now) => sent_now,
403                            Err(error) => {
404                                cursor.end_write();
405                                return Poll::Ready(Err(error));
406                            }
407                        };
408                        *sent += sent_now;
409                        if *sent < *target {
410                            *reported += sent_now;
411                            return Poll::Ready(Ok(sent_now));
412                        }
413                    }
414                    let result = ready!(op.poll_finish(cx));
415                    let reported = *reported;
416                    cursor.end_write();
417                    let (written, end) = result?;
418                    inner.position().store(end, Ordering::Relaxed);
419                    return Poll::Ready(written.checked_sub(reported).ok_or_else(|| {
420                        io::Error::new(
421                            io::ErrorKind::InvalidData,
422                            "file write acknowledged fewer bytes than were accepted",
423                        )
424                    }));
425                }
426                _ => return Poll::Ready(Err(in_progress("write polled"))),
427            }
428        }
429    }
430
431    /// Each write reaches the file when it completes, so there is nothing
432    /// buffered to push; flushing only has to settle work in flight.
433    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
434        let (inner, cursor) = self.get_mut().parts();
435        match &mut cursor.op {
436            Op::Idle | Op::Body { .. } => Poll::Ready(Ok(())),
437            Op::Read(op, at) => {
438                let at = *at;
439                let result = ready!(poll_read_op(op, cx));
440                cursor.op = Op::Idle;
441                cursor.op = Op::Body {
442                    body: result?,
443                    at,
444                    delivered: false,
445                };
446                Poll::Ready(Ok(()))
447            }
448            Op::Write { op, .. } => {
449                let result = ready!(op.poll_finish(cx));
450                cursor.end_write();
451                let (_, end) = result?;
452                inner.position().store(end, Ordering::Relaxed);
453                Poll::Ready(Ok(()))
454            }
455            Op::Size(op, delta) => {
456                let delta = *delta;
457                let result = ready!(Pin::new(op).poll(cx));
458                cursor.op = Op::Idle;
459                let end = offset_delta(result?, delta)?;
460                inner.position().store(end, Ordering::Relaxed);
461                Poll::Ready(Ok(()))
462            }
463        }
464    }
465
466    /// Only flushes: the file is closed by [`File::close`], or when dropped.
467    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
468        self.poll_flush(cx)
469    }
470}
471
472impl AsyncSeek for File {
473    /// An end-relative seek has to ask for the file's length, so it may be
474    /// pending; polling again with the same `position` drives that query.
475    fn poll_seek(
476        self: Pin<&mut Self>,
477        cx: &mut Context<'_>,
478        position: io::SeekFrom,
479    ) -> Poll<io::Result<u64>> {
480        let (inner, cursor) = self.get_mut().parts();
481        let pos = inner.position();
482        loop {
483            match (&mut cursor.op, position) {
484                (Op::Size(op, delta), io::SeekFrom::End(requested)) if *delta == requested => {
485                    let result = ready!(Pin::new(op).poll(cx));
486                    cursor.op = Op::Idle;
487                    pos.store(offset_delta(result?, requested)?, Ordering::Relaxed);
488                    return Poll::Ready(Ok(pos.load(Ordering::Relaxed)));
489                }
490                _ => cursor.drop_body(),
491            }
492            if cursor.is_busy() {
493                return Poll::Ready(Err(in_progress("seek started")));
494            }
495            match position {
496                // Both of these resolve locally: no syscall, and for the remote
497                // backend no round trip either.
498                io::SeekFrom::Start(offset) => pos.store(offset, Ordering::Relaxed),
499                io::SeekFrom::Current(delta) => {
500                    pos.store(
501                        offset_delta(pos.load(Ordering::Relaxed), delta)?,
502                        Ordering::Relaxed,
503                    );
504                }
505                // Only an end-relative seek needs the file's current length,
506                // and that answer is stale the moment it is produced — as it
507                // is for `lseek(SEEK_END)` too.
508                io::SeekFrom::End(delta) => {
509                    cursor.op = Op::Size(inner.start_size()?, delta);
510                    continue;
511                }
512            }
513            return Poll::Ready(Ok(pos.load(Ordering::Relaxed)));
514        }
515    }
516}