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;