Skip to main content

nuclei/fs/
file_ops.rs

1//! A file's calls beside its reads and writes: creating it, syncing it,
2//! its length, metadata, permissions and times, and a second handle to it.
3//!
4//! Each runs on the blocking pool after the handle's writes in flight landed
5//! (so a length sees them), on the handle's own descriptor, which a lease
6//! keeps open for the call: one dropped meanwhile leaves its object to the
7//! call, and none needs a descriptor of its own. A regular file's sync goes
8//! to the file's own IO state instead (`fs::file::durable`), on the shard's
9//! ring where it runs one.
10
11use core::pin::Pin;
12use std::fs::{File, FileTimes, Metadata, Permissions};
13use std::io;
14use std::path::Path;
15use std::time::SystemTime;
16
17use futures_io::AsyncWrite;
18
19use super::Allocate;
20use super::file::{SyncKind, sync_file};
21use super::ops::{OpenOptions, blocking};
22use crate::io::Handle;
23use crate::io::raw::Raw;
24use crate::io::sys;
25
26/// The descriptor of `file`, as the system calls take it.
27#[cfg(unix)]
28fn raw_of(file: &File) -> Raw {
29    std::os::fd::AsRawFd::as_raw_fd(file)
30}
31
32/// The handle of `file`, as the system calls take it.
33#[cfg(windows)]
34fn raw_of(file: &File) -> Raw {
35    std::os::windows::io::AsRawHandle::as_raw_handle(file) as usize as Raw
36}
37
38impl Handle<File> {
39    /// Opens the file at `path` for writing, creating it or cutting it down
40    /// to nothing.
41    pub async fn create(path: impl AsRef<Path>) -> io::Result<Handle<File>> {
42        OpenOptions::new()
43            .write(true)
44            .create(true)
45            .truncate(true)
46            .open(path)
47            .await
48    }
49
50    /// Creates the file at `path` and opens it for reading and writing,
51    /// failing if it exists.
52    pub async fn create_new(path: impl AsRef<Path>) -> io::Result<Handle<File>> {
53        OpenOptions::new()
54            .read(true)
55            .write(true)
56            .create_new(true)
57            .open(path)
58            .await
59    }
60
61    /// The options to open a file with.
62    pub fn options() -> OpenOptions {
63        OpenOptions::new()
64    }
65
66    /// Writes the file's data and metadata through to the device, as std's
67    /// `File::sync_all` does, once every write accepted before the call (at
68    /// the cursor, or positional and returned) landed. On io_uring the sync
69    /// is an operation of the shard's ring (`IORING_OP_FSYNC`); elsewhere a
70    /// job of the blocking pool.
71    ///
72    /// # Errors
73    ///
74    /// A write that failed (reported here as by a flush), or the sync's own
75    /// failure. Once a sync of the handle failed, every later one fails with
76    /// that error: the system reports a failure to write data back once, so
77    /// a later sync that succeeded would not mean the data reached the
78    /// device. Open the file again to start over.
79    pub async fn sync_all(&self) -> io::Result<()> {
80        if !self.is_regular() {
81            return self.on_pool(File::sync_all).await;
82        }
83        self.file()?.sync(SyncKind::All).await
84    }
85
86    /// Writes the file's data through to the device, and the metadata a
87    /// read needs, as std's `File::sync_data` does (`IORING_FSYNC_DATASYNC`
88    /// on io_uring); otherwise as [`sync_all`](Self::sync_all).
89    ///
90    /// # Errors
91    ///
92    /// As [`sync_all`](Self::sync_all).
93    pub async fn sync_data(&self) -> io::Result<()> {
94        if !self.is_regular() {
95            return self.on_pool(File::sync_data).await;
96        }
97        self.file()?.sync(SyncKind::Data).await
98    }
99
100    /// Writes back the dirty data of the `len` bytes of the file from
101    /// `offset` and waits for it (`len` of zero: to the end of the file), as
102    /// Linux's `sync_file_range` does, once every write accepted before the
103    /// call landed. On io_uring this is an operation of the shard's ring
104    /// (`IORING_OP_SYNC_FILE_RANGE`, for a range its length field holds, else
105    /// a job of the blocking pool).
106    ///
107    /// This is not a durability guarantee: it writes no metadata and flushes
108    /// no cache of the device, so a crash may still lose the data. Use
109    /// [`sync_data`](Self::sync_data) for that. On the systems that have no
110    /// such call (macOS, Windows, other unix) it does what `sync_data` does
111    /// for the whole file, which is stronger and covers the range.
112    ///
113    /// # Errors
114    ///
115    /// As [`sync_all`](Self::sync_all): a write that failed, or the call's
116    /// own failure, which every later sync of the handle then repeats (the
117    /// system reports a write-back error to one call only).
118    pub async fn sync_range(&self, offset: u64, len: u64) -> io::Result<()> {
119        let kind = SyncKind::Range { offset, len };
120        if !self.is_regular() {
121            return self.on_pool(move |file| sync_file(file, kind)).await;
122        }
123        self.file()?.sync(kind).await
124    }
125
126    /// Changes the space the `len` bytes of the file from `offset` use, as
127    /// `mode` says: reserves it, keeps the length, frees it, or zeroes it.
128    /// The call is a write of the range: it lands after every write accepted
129    /// at the cursor before it and every positional write that returned
130    /// before it, a read at the cursor made after it sees what it did, and a
131    /// future dropped while the call is out leaves it to the file, which
132    /// every later call of the handle waits for. On io_uring it is an
133    /// operation of the shard's ring (`fallocate(2)`); elsewhere a job of the
134    /// blocking pool (`fcntl` on macOS, `DeviceIoControl` and
135    /// `SetFileInformationByHandle` on Windows).
136    ///
137    /// A system that cannot do `mode` answers `Unsupported` (macOS cannot
138    /// zero a range in place), and a file system may refuse a mode as the
139    /// system says.
140    ///
141    /// # Errors
142    ///
143    /// `InvalidInput` when the end of the range is past the largest offset a
144    /// file can have; otherwise the system's own answer, among them that of
145    /// an empty range, a handle that cannot be written, and a stream.
146    pub async fn allocate(&self, offset: u64, len: u64, mode: Allocate) -> io::Result<()> {
147        if !self.is_regular() {
148            return self
149                .on_pool(move |file| sys::allocate(raw_of(file), offset, len, mode))
150                .await;
151        }
152        self.file()?.allocate(offset, len, mode).await
153    }
154
155    /// Sets the file's length: cut down, or grown with zeros. The cursor
156    /// stays where it is. Bytes of the file read ahead before the cut are
157    /// never handed out after it.
158    pub async fn set_len(&self, len: u64) -> io::Result<()> {
159        let result = self.on_pool(move |file| file.set_len(len)).await;
160        if let Some(file) = self.file.get() {
161            file.moved_on();
162        }
163        result
164    }
165
166    /// The file's metadata.
167    pub async fn metadata(&self) -> io::Result<Metadata> {
168        self.on_pool(File::metadata).await
169    }
170
171    /// Sets the file's permissions.
172    pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
173        self.on_pool(move |file| file.set_permissions(perm)).await
174    }
175
176    /// Sets the file's access and modification times.
177    pub async fn set_times(&self, times: FileTimes) -> io::Result<()> {
178        self.on_pool(move |file| file.set_times(times)).await
179    }
180
181    /// Sets the file's modification time.
182    pub async fn set_modified(&self, time: SystemTime) -> io::Result<()> {
183        self.on_pool(move |file| file.set_modified(time)).await
184    }
185
186    /// A second handle to the same open file, with a cursor of its own
187    /// starting where the file's offset is (writes in flight landed first).
188    pub async fn try_clone(&self) -> io::Result<Handle<File>> {
189        let file = self.on_pool(File::try_clone).await?;
190        Handle::new(file)
191    }
192
193    /// Runs `call` on the blocking pool once the writes in flight landed, on
194    /// the handle's own descriptor, leased for the call.
195    async fn on_pool<T, F>(&self, call: F) -> io::Result<T>
196    where
197        T: Send + 'static,
198        F: FnOnce(&File) -> io::Result<T> + Send + 'static,
199    {
200        let mut this = self;
201        core::future::poll_fn(|cx| Pin::new(&mut this).poll_flush(cx)).await?;
202        let lease = self.source().lease()?;
203        blocking(move || call(&lease.file())).await
204    }
205}