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}