Skip to main content

nuclei/fs/
mod.rs

1//! The file system: files as [`File`] (std's file in a
2//! [`Handle`]), [`OpenOptions`], the calls by path
3//! ([`read`], [`write`](fn@write), [`metadata`], [`rename`], [`copy`], ...),
4//! directories ([`create_dir_all`], [`DirBuilder`], [`read_dir`] streamed a
5//! page at a time) and links. Calls by path, and a regular file's calls
6//! beside its reads, writes, syncs and [`Handle::allocate`], run on the
7//! blocking pool; [`Handle::try_lock_file`], which never waits, runs on the
8//! calling thread, and so does `File::open` on Linux for a regular file on a
9//! local file system whose path the kernel's caches resolve at once.
10//!
11//! A regular file's reads and writes, at the handle's own cursor or at an
12//! offset of the caller's ([`Handle::read_at`], [`Handle::write_at`] and
13//! their forms, which many tasks may make at once on one handle, and
14//! [`Handle::read_many_at`], many reads in one call), its syncs
15//! ([`Handle::sync_all`], [`Handle::sync_data`], [`Handle::sync_range`]) and
16//! [`Handle::allocate`] are operations of the shard's ring on io_uring and
17//! jobs of the blocking pool on the other backends (see `file`). A file
18//! opened for direct IO (`O_DIRECT`, `FILE_FLAG_NO_BUFFERING`) is read and
19//! written with exactly the caller's lengths and offsets, never through the
20//! handle's read-ahead or write-behind. A `File` that is not a regular file
21//! (a FIFO, a terminal, a pipe) is a byte stream the shard's driver serves
22//! like a socket (on Windows, through the completion port, or through jobs
23//! of the blocking pool for a handle opened for synchronous IO). Reads and
24//! writes at the cursor are `AsyncRead` and `AsyncWrite` on the handle and
25//! on a shared reference to it (both share the cursor, as `&File` shares its
26//! offset); `AsyncBufRead` and `AsyncSeek` are on the handle. `read_owned`,
27//! `write_owned`, `read_at_owned`, `write_at_owned` and the vectored
28//! `read_vectored_owned` and `write_vectored_owned` take the caller's
29//! buffers (any [`IoBuf`] or [`IoBufMut`], an
30//! [`AlignedBuf`](crate::io::AlignedBuf) for direct IO) and give them back,
31//! so the bytes are read into them and written from them with no copy.
32//!
33//! Beside those, a regular file can be asked to change the space a range
34//! uses ([`Handle::allocate`]: reserve, keep the length, punch a hole, zero),
35//! to write a range back ([`Handle::sync_range`]), to copy a range into
36//! another file in the kernel where the system can ([`Handle::copy_range_to`]),
37//! and to take an advisory lock without blocking the worker
38//! ([`Handle::lock_file`], [`Handle::try_lock_file`], [`Handle::unlock_file`]).
39//!
40//! A file's name in its directory needs [`sync_dir`] to survive a crash. To
41//! replace a file atomically: write a temporary file in the same directory,
42//! `sync_all` it, [`rename`] it over the target, then [`sync_dir`] the
43//! directory.
44
45mod allocate;
46pub(crate) mod call;
47mod copy_range;
48pub(crate) mod file;
49mod file_ops;
50mod lock;
51#[cfg(target_os = "linux")]
52mod open_cached;
53mod ops;
54mod read_dir;
55#[cfg(ring)]
56pub(crate) mod ring;
57mod sync_dir;
58
59pub use self::allocate::Allocate;
60pub use std::fs::{FileTimes, FileType, Metadata, Permissions, TryLockError};
61
62#[cfg(unix)]
63pub use self::ops::symlink;
64pub use self::ops::{
65    DirBuilder, OpenOptions, canonicalize, copy, create_dir, create_dir_all, hard_link, metadata,
66    read, read_link, read_to_string, remove_dir, remove_dir_all, remove_file, rename,
67    set_permissions, symlink_metadata, try_exists, write,
68};
69#[cfg(windows)]
70pub use self::ops::{symlink_dir, symlink_file};
71pub use self::read_dir::{DirEntry, ReadDir, read_dir};
72pub use self::sync_dir::sync_dir;
73
74/// A file: std's `File` in a [`Handle`], with async reads, writes, seeks and
75/// calls.
76pub type File = Handle<std::fs::File>;
77
78use core::pin::Pin;
79use core::task::{Context, Poll};
80use std::fs::File as StdFile;
81use std::io::{self, SeekFrom};
82use std::path::Path;
83
84use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite};
85
86use crate::buf::owned::{IoBuf, IoBufMut, OwnedBuf};
87use crate::buf::vectored::{self, IoVecBuf, IoVecBufMut};
88use crate::io::Handle;
89use crate::io::source::Kind;
90
91/// A file's calls. std's own `File::lock`, `FileExt::read_at` and
92/// `FileExt::write_at` (on Windows `seek_read` and `seek_write`), reached
93/// through `Deref`, block the worker thread that makes them. Use the async
94/// calls instead: [`lock_file`](Handle::lock_file),
95/// [`try_lock_file`](Handle::try_lock_file) and
96/// [`unlock_file`](Handle::unlock_file), [`read_at`](Handle::read_at) and
97/// [`write_at`](Handle::write_at), and, with no copy of the bytes,
98/// [`read_at_owned`](Handle::read_at_owned) and
99/// [`write_at_owned`](Handle::write_at_owned).
100impl Handle<StdFile> {
101    /// Opens the file at `p` for reading: on the calling thread when it is a
102    /// regular file on a local file system and the kernel's caches answer at
103    /// once (Linux), on the blocking pool otherwise.
104    ///
105    /// # Errors
106    ///
107    /// As [`File::open`].
108    pub async fn open(p: impl AsRef<Path>) -> io::Result<Handle<StdFile>> {
109        #[cfg(target_os = "linux")]
110        if let Some(file) = open_cached::open(p.as_ref()) {
111            return Handle::adopt(file, Kind::File);
112        }
113        let path = p.as_ref().to_path_buf();
114        let file = ops::blocking(move || StdFile::open(path)).await?;
115        Handle::new(file)
116    }
117
118    fn is_regular(&self) -> bool {
119        self.source.kind() == Kind::File
120    }
121
122    /// Reads at the cursor into the spare capacity of `buf`, after the bytes
123    /// it holds, and gives `buf` back with how many bytes came: zero at the
124    /// end of the file, or when `buf` has no spare capacity. The bytes are
125    /// read into `buf` itself, never copied, once the writes before the read
126    /// landed.
127    ///
128    /// If the future is dropped before it completes, the cursor stays where
129    /// it was. `buf` is any [`IoBufMut`]: a `Vec<u8>`, an
130    /// [`AlignedBuf`](crate::io::AlignedBuf) for a file opened for direct IO,
131    /// or a type of the caller's.
132    pub async fn read_owned<B>(&self, buf: B) -> (io::Result<usize>, B)
133    where
134        B: IoBufMut,
135    {
136        if !self.is_regular() {
137            return self.source().read_owned(buf).await;
138        }
139        match self.file() {
140            Ok(file) => file.read_owned(buf).await,
141            Err(err) => (Err(err), buf),
142        }
143    }
144
145    /// Writes all of `buf` at the cursor (at the end, for a file opened to
146    /// append), after the writes before it, and gives it back with its
147    /// length once the bytes reached the file. They are written from `buf`
148    /// itself, never copied.
149    ///
150    /// If the future is dropped before it completes, the write is withdrawn
151    /// while it still waits for the writes before it, and lands whole, in
152    /// its place, once it started. `buf` is any [`IoBuf`], as for
153    /// [`read_owned`](Self::read_owned).
154    pub async fn write_owned<B>(&self, buf: B) -> (io::Result<usize>, B)
155    where
156        B: IoBuf,
157    {
158        if !self.is_regular() {
159            return self.source().write_owned(buf).await;
160        }
161        match self.file() {
162            Ok(file) => file.write_owned(buf).await,
163            Err(err) => (Err(err), buf),
164        }
165    }
166
167    /// Reads at the cursor into the spare capacity of `bufs`, after the
168    /// bytes each holds, filling each buffer's room before the next one's,
169    /// as [`read_owned`](Self::read_owned) reads into one, and gives `bufs`
170    /// back with how many bytes came: zero at the end of the file, or when no
171    /// buffer has spare capacity. One call of the kernel takes every room
172    /// (`preadv`, or `IORING_OP_READV` on the ring); on Windows, which has no
173    /// vectored read of a buffered file, one read a buffer, in order, until
174    /// one comes short.
175    pub async fn read_vectored_owned<V>(&self, bufs: V) -> (io::Result<usize>, V)
176    where
177        V: IoVecBufMut,
178    {
179        let bufs = OwnedBuf::vectored_read(vectored::filled(bufs));
180        let (result, bufs) = self.read_owned(bufs).await;
181        (result, vectored::into_filled(bufs.into_segments()))
182    }
183
184    /// Writes all of the bytes of `bufs`, each buffer's in order, at the
185    /// cursor as one write (as [`write_owned`](Self::write_owned) writes
186    /// one buffer), and gives `bufs` back with how many bytes that was. No
187    /// other write of the handle comes between two of them. One call of the
188    /// kernel takes every buffer left (`pwritev`, `writev` for a file opened
189    /// to append, or `IORING_OP_WRITEV` on the ring; on Windows, which has no
190    /// vectored write of a buffered file, one write a buffer, in order), from
191    /// the buffers themselves, never copied; on a file opened for direct IO
192    /// each buffer's memory and length meet the kernel's alignment rules on
193    /// their own.
194    pub async fn write_vectored_owned<V>(&self, bufs: V) -> (io::Result<usize>, V)
195    where
196        V: IoVecBuf,
197    {
198        let bufs = OwnedBuf::vectored(vectored::written(bufs));
199        let (result, bufs) = self.write_owned(bufs).await;
200        (result, vectored::into_written(bufs.into_segments()))
201    }
202}
203
204macro_rules! file_io {
205    ($($ty:ty),*) => {$(
206        impl AsyncRead for $ty {
207            fn poll_read(
208                self: Pin<&mut Self>,
209                cx: &mut Context<'_>,
210                buf: &mut [u8],
211            ) -> Poll<io::Result<usize>> {
212                if !self.is_regular() {
213                    return self.source().poll_read(cx, buf, false);
214                }
215                match self.file() {
216                    Ok(file) => file.poll_read(cx, buf),
217                    Err(err) => Poll::Ready(Err(err)),
218                }
219            }
220
221            fn poll_read_vectored(
222                self: Pin<&mut Self>,
223                cx: &mut Context<'_>,
224                bufs: &mut [io::IoSliceMut<'_>],
225            ) -> Poll<io::Result<usize>> {
226                if !self.is_regular() {
227                    return self.source().poll_read_vectored(cx, bufs);
228                }
229                match self.file() {
230                    Ok(file) => file.poll_read_vectored(cx, bufs),
231                    Err(err) => Poll::Ready(Err(err)),
232                }
233            }
234        }
235
236        impl AsyncWrite for $ty {
237            fn poll_write(
238                self: Pin<&mut Self>,
239                cx: &mut Context<'_>,
240                buf: &[u8],
241            ) -> Poll<io::Result<usize>> {
242                if !self.is_regular() {
243                    return self.source().poll_write(cx, buf);
244                }
245                match self.file() {
246                    Ok(file) => file.poll_write(cx, buf),
247                    Err(err) => Poll::Ready(Err(err)),
248                }
249            }
250
251            fn poll_write_vectored(
252                self: Pin<&mut Self>,
253                cx: &mut Context<'_>,
254                bufs: &[io::IoSlice<'_>],
255            ) -> Poll<io::Result<usize>> {
256                if !self.is_regular() {
257                    return self.source().poll_write_vectored(cx, bufs);
258                }
259                match self.file() {
260                    Ok(file) => file.poll_write_vectored(cx, bufs),
261                    Err(err) => Poll::Ready(Err(err)),
262                }
263            }
264
265            fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
266                if !self.is_regular() {
267                    return self.source().poll_flush(cx);
268                }
269                match self.file() {
270                    Ok(file) => file.poll_flush(cx),
271                    Err(err) => Poll::Ready(Err(err)),
272                }
273            }
274
275            fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
276                // A file has no half to shut down: closing is flushing.
277                self.poll_flush(cx)
278            }
279        }
280    )*};
281}
282
283file_io!(Handle<StdFile>, &Handle<StdFile>);
284
285impl AsyncSeek for Handle<StdFile> {
286    fn poll_seek(
287        self: Pin<&mut Self>,
288        cx: &mut Context<'_>,
289        pos: SeekFrom,
290    ) -> Poll<io::Result<u64>> {
291        if !self.is_regular() {
292            return Poll::Ready(Err(io::Error::new(
293                io::ErrorKind::Unsupported,
294                "the file is a stream, which has no position to seek",
295            )));
296        }
297        match self.file() {
298            Ok(file) => file.poll_seek(cx, pos),
299            Err(err) => Poll::Ready(Err(err)),
300        }
301    }
302}
303
304impl AsyncBufRead for Handle<StdFile> {
305    fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
306        let this = Pin::into_inner(self);
307        if !this.is_regular() {
308            return Poll::Ready(Err(io::Error::new(
309                io::ErrorKind::Unsupported,
310                "buffered reads are for regular files; wrap a stream in a buffered reader",
311            )));
312        }
313        let file = match this.file() {
314            Ok(file) => file,
315            Err(err) => return Poll::Ready(Err(err)),
316        };
317        file.poll_fill_buf(cx).map_ok(|(ptr, len)| {
318            // SAFETY: the bytes are read ahead into a pool buffer the state
319            // keeps until a call that consumes, writes or seeks; each of
320            // those needs this handle, which the slice borrows exclusively,
321            // and no job writes into a buffer the state holds.
322            unsafe { core::slice::from_raw_parts(ptr, len) }
323        })
324    }
325
326    fn consume(self: Pin<&mut Self>, amt: usize) {
327        if let Some(file) = self.file.get() {
328            file.consume(amt);
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests;