Skip to main content

nuclei/fs/file/
positional.rs

1//! Reads and writes at offsets of the caller's own: [`Handle::read_at`],
2//! [`Handle::write_at`] and their forms. They never read or move the
3//! handle's cursor or its epoch, any number of them run at once on one
4//! handle, and they keep these rules with the calls at the cursor:
5//!
6//! - A positional call first waits for the cursor writes accepted before it
7//!   (the write in flight and the write-behind) and for the positional
8//!   writes left by their tasks: a positional write is never lost under, or
9//!   reordered with, a write that came before it, and a positional read sees
10//!   those writes.
11//! - When a positional write's task takes its end, the file's generation
12//!   moves on: a cursor read made after it sees what it wrote, since a read
13//!   job or bytes read ahead count only in the generation they began in. A
14//!   positional write still out while a cursor read is taken races it, as
15//!   `pwrite` and `pread` from two threads race.
16//! - A positional write whose future is dropped while it is out still lands;
17//!   the file keeps it until it did (`orphans`).
18//! - The writes of a range (`space`: `allocate`; `copy`: the write side of
19//!   `copy_range_to`) are positional writes in all this, and so is the cut of
20//!   `set_len`, which moves the generation on when it ran.
21//!
22//! On io_uring each is a read or a write of the shard's ring at its offset;
23//! elsewhere a `pread` or `pwrite` on the blocking pool (on Windows a
24//! `ReadFile` or `WriteFile` at the offset its `OVERLAPPED` names).
25//! `proofs/tla/lent/LentFile.tla` (scenario "positional") checks the rules.
26
27use crate::buf::owned::{IoBuf, IoBufMut, OwnedBuf};
28use core::future::poll_fn;
29use core::task::{Context, Poll, Waker};
30use std::fs::File as StdFile;
31use std::io;
32use std::sync::Arc;
33
34use super::FileIo;
35use super::orphans::Orphan;
36use super::work::{Counted, Ended, Work, bounce, tally_read, tally_write};
37use crate::fs::call::End;
38use crate::io::Handle;
39use crate::io::side::After;
40
41impl FileIo {
42    /// Ready once every cursor write accepted before the call, and every
43    /// positional write left by its task, landed.
44    pub(super) fn poll_ordered(self: &Arc<Self>, cx: &mut Context<'_>) -> Poll<()> {
45        if !self.has_unsent() {
46            return Poll::Ready(());
47        }
48        self.with_state(cx, |st, io, waker, after| st.poll_writes(io, waker, after))
49    }
50
51    /// A buffer of the pool for the bytes of a task's memory at `addr`, and
52    /// where in it and how many of `len` the call moves (see `work::bounce`).
53    pub(super) async fn bounce_for(
54        &self,
55        len: usize,
56        addr: usize,
57    ) -> (crate::buf::PoolBuf, usize, usize) {
58        poll_fn(|cx| match bounce(len, addr, self.direct, cx.waker()) {
59            Some(got) => Poll::Ready(got),
60            None => Poll::Pending,
61        })
62        .await
63    }
64
65    pub(crate) async fn read_at(self: &Arc<Self>, out: &mut [u8], at: u64) -> io::Result<usize> {
66        if out.is_empty() {
67            return Ok(0);
68        }
69        poll_fn(|cx| self.poll_ordered(cx)).await;
70        let (buf, from, n) = self.bounce_for(out.len(), out.as_ptr() as usize).await;
71        let mut job = self.submit_read(buf, from, n, at);
72        let pooled = job.pooled();
73        let result = poll_fn(|cx| job.poll(cx.waker())).await;
74        tally_read(pooled, &result, |buf| buf.filled().len());
75        Ok(result?.peek_into(out))
76    }
77
78    pub(crate) async fn write_at(self: &Arc<Self>, bytes: &[u8], at: u64) -> io::Result<usize> {
79        if bytes.is_empty() {
80            return Ok(0);
81        }
82        poll_fn(|cx| self.poll_ordered(cx)).await;
83        let (mut buf, from, n) = self.bounce_for(bytes.len(), bytes.as_ptr() as usize).await;
84        buf.fill_at(from, &bytes[..n]);
85        let (work, bytes) = self.submit_pwrite(buf, at, Waker::noop());
86        let mut out = Out {
87            io: self,
88            work: Some(work),
89            bytes,
90        };
91        poll_fn(|cx| out.poll(cx)).await.map(|()| n)
92    }
93
94    pub(crate) async fn read_at_owned<B>(
95        self: &Arc<Self>,
96        buf: B,
97        at: u64,
98    ) -> (io::Result<usize>, B)
99    where
100        B: IoBufMut,
101    {
102        let buf = OwnedBuf::filled(buf);
103        if buf.len() == buf.capacity() {
104            return (Ok(0), buf.into_filled());
105        }
106        poll_fn(|cx| self.poll_ordered(cx)).await;
107        let mut job = self.submit_read_owned(&mut Some(buf), at, Waker::noop());
108        let pooled = job.pooled();
109        let (result, buf) = poll_fn(|cx| job.poll(cx.waker())).await;
110        tally_read(pooled, &result, |n| *n);
111        (result, buf.into_filled())
112    }
113
114    pub(crate) async fn write_at_owned<B>(
115        self: &Arc<Self>,
116        buf: B,
117        at: u64,
118    ) -> (io::Result<usize>, B)
119    where
120        B: IoBuf,
121    {
122        let buf = OwnedBuf::written(buf);
123        if buf.is_empty() {
124            return (Ok(0), buf.into_written());
125        }
126        poll_fn(|cx| self.poll_ordered(cx)).await;
127        let len = buf.len();
128        let (work, bytes) = self.submit_pwrite_owned(buf, at, Waker::noop());
129        let mut out = Out {
130            io: self,
131            work: Some(work),
132            bytes,
133        };
134        let (result, buf) = poll_fn(|cx| out.poll(cx)).await;
135        (result.map(|()| len), buf.into_written())
136    }
137
138    /// The file may hold other bytes than before (a write ended, failed or
139    /// not; a cut, a hole): reads of the file before this, read ahead or still
140    /// out, are of an older generation.
141    pub(crate) fn moved_on(&self) {
142        // Release: pairs with the Acquire of `FileIo::generation`.
143        self.generation
144            .fetch_add(1, core::sync::atomic::Ordering::Release);
145    }
146
147    /// Leaves `orphan`, a positional write whose task went away, to the
148    /// file state, which keeps everything after it waiting until it landed.
149    fn orphan(self: &Arc<Self>, orphan: Orphan) {
150        self.orphans.push(orphan);
151        let mut after = After::default();
152        self.state.defer(
153            super::deferred::ORPHAN,
154            &mut after,
155            &super::FileHooks { io: self },
156        );
157        // A call after this one looks at the state: its holder keeps the
158        // flag up while the write is out.
159        self.unsent
160            .store(true, core::sync::atomic::Ordering::Release);
161        after.run();
162    }
163}
164
165/// A positional write out, whose task takes its end. Dropped first, the
166/// write is left to the file. `bytes` are counted when it ends, as for a
167/// cursor write (see [`Writing`](super::work::Writing)): 0 for a ring's, or
168/// for a change of a range.
169pub(super) struct Out<'a, T>
170where
171    (Work<T>, Counted): Into<Orphan>,
172{
173    pub(super) io: &'a Arc<FileIo>,
174    pub(super) work: Option<Work<T>>,
175    pub(super) bytes: Counted,
176}
177
178impl<T> Out<'_, T>
179where
180    T: Ended + End,
181    (Work<T>, Counted): Into<Orphan>,
182{
183    /// The call's end, once it came.
184    pub(super) async fn finish(mut self) -> T {
185        poll_fn(|cx| self.poll(cx)).await
186    }
187
188    pub(super) fn poll(&mut self, cx: &mut Context<'_>) -> Poll<T> {
189        let Some(work) = self.work.as_mut() else {
190            return Poll::Pending;
191        };
192        let end = core::task::ready!(work.poll(cx.waker()));
193        self.work = None;
194        tally_write(self.bytes, end.error().is_none());
195        self.io.moved_on();
196        Poll::Ready(end)
197    }
198}
199
200impl<T> Drop for Out<'_, T>
201where
202    (Work<T>, Counted): Into<Orphan>,
203{
204    fn drop(&mut self) {
205        if let Some(work) = self.work.take() {
206            self.io.orphan((work, self.bytes).into());
207        }
208    }
209}
210
211impl Handle<StdFile> {
212    /// Reads at `offset` into `buf` and answers how many bytes came, zero at
213    /// the end of the file, as std's `FileExt::read_at` does (on Windows
214    /// `seek_read`, after which the system's own position of the file is
215    /// past the bytes read, as std documents; the handle's cursor never
216    /// moves).
217    ///
218    /// The handle's cursor and the reads and writes at it are left as they
219    /// are, and any number of positional calls may run at once on one
220    /// handle. The read sees every write accepted at the cursor before it,
221    /// and every positional write that returned before it.
222    ///
223    /// The bytes come through a buffer of the runtime's pool, at most 64 KiB
224    /// a call (a short read, as `read_at` may answer). On a file opened for
225    /// direct IO the call reads exactly `buf.len()` bytes at `offset`, into
226    /// memory placed in its page as `buf` is, so the kernel refuses (with
227    /// `EINVAL`) exactly what it would refuse of `buf` itself.
228    /// [`read_at_owned`](Self::read_at_owned) reads into the caller's own
229    /// buffer with no copy.
230    ///
231    /// # Errors
232    ///
233    /// As the system's positional read: on a stream (a pipe, a terminal) the
234    /// file has no offset to read at.
235    pub async fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
236        self.file()?.read_at(buf, offset).await
237    }
238
239    /// Writes `buf` at `offset` and answers how many bytes landed, as std's
240    /// `FileExt::write_at` does (on Windows `seek_write`, after which the
241    /// system's own position of the file is past the bytes written; the
242    /// handle's cursor never moves). On Linux a file opened to append takes
243    /// the bytes at its end whatever the offset, as `pwrite(2)` does there.
244    ///
245    /// The write lands after every write accepted at the cursor before it,
246    /// and before any write the handle accepts after it returned; positional
247    /// writes running at once land in any order, as `pwrite` from two
248    /// threads does. If the future is dropped while the write is out, it
249    /// still lands, and every later call of the handle waits for it.
250    ///
251    /// The bytes go through a buffer of the runtime's pool, at most 64 KiB a
252    /// call (a short write, as `write_at` may answer). On a file opened for
253    /// direct IO the call writes exactly `buf` at `offset`, from memory
254    /// placed in its page as `buf` is, so the kernel refuses (with `EINVAL`)
255    /// exactly what it would refuse of `buf` itself.
256    /// [`write_at_owned`](Self::write_at_owned) writes from the caller's own
257    /// buffer with no copy.
258    ///
259    /// # Errors
260    ///
261    /// As the system's positional write.
262    pub async fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
263        self.file()?.write_at(buf, offset).await
264    }
265
266    /// Reads exactly `buf.len()` bytes at `offset`, as std's
267    /// `FileExt::read_exact_at` does, with [`read_at`](Self::read_at).
268    ///
269    /// # Errors
270    ///
271    /// `UnexpectedEof` when the file ends first (the bytes read so far are in
272    /// `buf`), or the first error of a read.
273    pub async fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
274        while !buf.is_empty() {
275            match self.read_at(buf, offset).await {
276                Ok(0) => {
277                    return Err(io::Error::new(
278                        io::ErrorKind::UnexpectedEof,
279                        "failed to fill whole buffer",
280                    ));
281                }
282                Ok(n) => {
283                    buf = &mut buf[n..];
284                    offset += n as u64;
285                }
286                Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
287                Err(err) => return Err(err),
288            }
289        }
290        Ok(())
291    }
292
293    /// Writes all of `buf` at `offset`, as std's `FileExt::write_all_at`
294    /// does, with [`write_at`](Self::write_at).
295    ///
296    /// # Errors
297    ///
298    /// `WriteZero` when a write takes nothing, or the first error of a write.
299    pub async fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
300        while !buf.is_empty() {
301            match self.write_at(buf, offset).await {
302                Ok(0) => {
303                    return Err(io::Error::new(
304                        io::ErrorKind::WriteZero,
305                        "failed to write whole buffer",
306                    ));
307                }
308                Ok(n) => {
309                    buf = &buf[n..];
310                    offset += n as u64;
311                }
312                Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
313                Err(err) => return Err(err),
314            }
315        }
316        Ok(())
317    }
318
319    /// Reads at `offset` into the spare capacity of `buf`, after the bytes
320    /// it holds, in one call of the system's positional read, and gives
321    /// `buf` back with how many bytes came: zero at the end of the file, or
322    /// when `buf` has no spare capacity. The bytes are read into `buf`
323    /// itself, never copied; on a file opened for direct IO the kernel's
324    /// alignment rules apply to `buf`'s own memory, which an
325    /// [`AlignedBuf`](crate::io::AlignedBuf) aligns. `buf` is any
326    /// [`IoBufMut`](crate::io::IoBufMut). Otherwise as
327    /// [`read_at`](Self::read_at).
328    pub async fn read_at_owned<B>(&self, buf: B, offset: u64) -> (io::Result<usize>, B)
329    where
330        B: IoBufMut,
331    {
332        match self.file() {
333            Ok(file) => file.read_at_owned(buf, offset).await,
334            Err(err) => (Err(err), buf),
335        }
336    }
337
338    /// Writes all of `buf` at `offset` and gives it back with its length
339    /// once the bytes landed. They are written from `buf` itself, never
340    /// copied; on a file opened for direct IO the kernel's alignment rules
341    /// apply to `buf`'s own memory, which an
342    /// [`AlignedBuf`](crate::io::AlignedBuf) aligns. If the future is dropped
343    /// while the write is out, it still lands, `buf` going with it. `buf` is
344    /// any [`IoBuf`](crate::io::IoBuf). Otherwise as
345    /// [`write_at`](Self::write_at).
346    pub async fn write_at_owned<B>(&self, buf: B, offset: u64) -> (io::Result<usize>, B)
347    where
348        B: IoBuf,
349    {
350        match self.file() {
351            Ok(file) => file.write_at_owned(buf, offset).await,
352            Err(err) => (Err(err), buf),
353        }
354    }
355}