monoio/fs/open_options.rs
1#[cfg(unix)]
2use std::os::unix::prelude::OpenOptionsExt;
3use std::{io, path::Path};
4
5#[cfg(windows)]
6use windows_sys::Win32::{
7 Foundation::{ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE},
8 Security::SECURITY_ATTRIBUTES,
9 Storage::FileSystem::{
10 CREATE_ALWAYS, CREATE_NEW, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_WRITE,
11 FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_ALWAYS,
12 OPEN_EXISTING, TRUNCATE_EXISTING,
13 },
14};
15
16use crate::{
17 driver::{op::Op, shared_fd::SharedFd},
18 fs::File,
19};
20
21/// Options and flags which can be used to configure how a file is opened.
22///
23/// This builder exposes the ability to configure how a [`File`] is opened and
24/// what operations are permitted on the open file. The [`File::open`] and
25/// [`File::create`] methods are aliases for commonly used options using this
26/// builder.
27///
28/// Generally speaking, when using `OpenOptions`, you'll first call
29/// [`OpenOptions::new`], then chain calls to methods to set each option, then
30/// call [`OpenOptions::open`], passing the path of the file you're trying to
31/// open. This will give you a [`io::Result`] with a [`File`] inside that you
32/// can further operate on.
33///
34/// # Examples
35///
36/// Opening a file to read:
37///
38/// ```no_run
39/// use monoio::fs::OpenOptions;
40///
41/// #[monoio::main]
42/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
43/// let file = OpenOptions::new().read(true).open("foo.txt").await?;
44/// Ok(())
45/// }
46/// ```
47///
48/// Opening a file for both reading and writing, as well as creating it if it
49/// doesn't exist:
50///
51/// ```no_run
52/// use monoio::fs::OpenOptions;
53///
54/// #[monoio::main]
55/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
56/// let file = OpenOptions::new()
57/// .read(true)
58/// .write(true)
59/// .create(true)
60/// .open("foo.txt")
61/// .await?;
62/// Ok(())
63/// }
64/// ```
65#[derive(Debug, Clone)]
66pub struct OpenOptions {
67 read: bool,
68 write: bool,
69 append: bool,
70 truncate: bool,
71 create: bool,
72 create_new: bool,
73 #[cfg(unix)]
74 pub(crate) mode: libc::mode_t,
75 #[cfg(unix)]
76 pub(crate) custom_flags: libc::c_int,
77 #[cfg(windows)]
78 pub(crate) custom_flags: u32,
79 #[cfg(windows)]
80 pub(crate) access_mode: Option<u32>,
81 #[cfg(windows)]
82 pub(crate) attributes: u32,
83 #[cfg(windows)]
84 pub(crate) share_mode: u32,
85 #[cfg(windows)]
86 pub(crate) security_qos_flags: u32,
87 #[cfg(windows)]
88 pub(crate) security_attributes: *mut SECURITY_ATTRIBUTES,
89}
90
91impl OpenOptions {
92 /// Creates a blank new set of options ready for configuration.
93 ///
94 /// All options are initially set to `false`.
95 ///
96 /// # Examples
97 ///
98 /// ```no_run
99 /// use monoio::fs::OpenOptions;
100 ///
101 /// #[monoio::main]
102 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
103 /// let file = OpenOptions::new().read(true).open("foo.txt").await?;
104 /// Ok(())
105 /// }
106 /// ```
107 #[allow(clippy::new_without_default)]
108 pub fn new() -> OpenOptions {
109 OpenOptions {
110 // generic
111 read: false,
112 write: false,
113 append: false,
114 truncate: false,
115 create: false,
116 create_new: false,
117 #[cfg(unix)]
118 mode: 0o666,
119 #[cfg(unix)]
120 custom_flags: 0,
121 #[cfg(windows)]
122 custom_flags: 0,
123 #[cfg(windows)]
124 access_mode: None,
125 #[cfg(windows)]
126 share_mode: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
127 #[cfg(windows)]
128 attributes: 0,
129 #[cfg(windows)]
130 security_qos_flags: 0,
131 #[cfg(windows)]
132 security_attributes: std::ptr::null_mut(),
133 }
134 }
135
136 /// Sets the option for read access.
137 ///
138 /// This option, when true, will indicate that the file should be
139 /// `read`-able if opened.
140 ///
141 /// # Examples
142 ///
143 /// ```no_run
144 /// use monoio::fs::OpenOptions;
145 ///
146 /// #[monoio::main]
147 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
148 /// let file = OpenOptions::new().read(true).open("foo.txt").await?;
149 /// Ok(())
150 /// }
151 /// ```
152 pub fn read(&mut self, read: bool) -> &mut OpenOptions {
153 self.read = read;
154 self
155 }
156
157 /// Sets the option for write access.
158 ///
159 /// This option, when true, will indicate that the file should be
160 /// `write`-able if opened.
161 ///
162 /// If the file already exists, any write calls on it will overwrite its
163 /// contents, without truncating it.
164 ///
165 /// # Examples
166 ///
167 /// ```no_run
168 /// use monoio::fs::OpenOptions;
169 ///
170 /// #[monoio::main]
171 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
172 /// let file = OpenOptions::new().write(true).open("foo.txt").await?;
173 /// Ok(())
174 /// }
175 /// ```
176 pub fn write(&mut self, write: bool) -> &mut OpenOptions {
177 self.write = write;
178 self
179 }
180
181 /// Sets the option for the append mode.
182 ///
183 /// This option, when true, means that writes will append to a file instead
184 /// of overwriting previous contents. Note that setting
185 /// `.write(true).append(true)` has the same effect as setting only
186 /// `.append(true)`.
187 ///
188 /// For most filesystems, the operating system guarantees that all writes
189 /// are atomic: no writes get mangled because another process writes at the
190 /// same time.
191 ///
192 /// ## Note
193 ///
194 /// This function doesn't create the file if it doesn't exist. Use the
195 /// [`OpenOptions::create`] method to do so.
196 ///
197 /// # Examples
198 ///
199 /// ```no_run
200 /// use monoio::fs::OpenOptions;
201 ///
202 /// #[monoio::main]
203 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
204 /// let file = OpenOptions::new().append(true).open("foo.txt").await?;
205 /// Ok(())
206 /// }
207 /// ```
208 pub fn append(&mut self, append: bool) -> &mut OpenOptions {
209 self.append = append;
210 self
211 }
212
213 /// Sets the option for truncating a previous file.
214 ///
215 /// If a file is successfully opened with this option set it will truncate
216 /// the file to 0 length if it already exists.
217 ///
218 /// The file must be opened with write access for truncate to work.
219 ///
220 /// # Examples
221 ///
222 /// ```no_run
223 /// use monoio::fs::OpenOptions;
224 ///
225 /// #[monoio::main]
226 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
227 /// let file = OpenOptions::new()
228 /// .write(true)
229 /// .truncate(true)
230 /// .open("foo.txt")
231 /// .await?;
232 /// Ok(())
233 /// }
234 /// ```
235 pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions {
236 self.truncate = truncate;
237 self
238 }
239
240 /// Sets the option to create a new file, or open it if it already exists.
241 ///
242 /// In order for the file to be created, [`OpenOptions::write`] or
243 /// [`OpenOptions::append`] access must be used.
244 ///
245 /// # Examples
246 ///
247 /// ```no_run
248 /// use monoio::fs::OpenOptions;
249 ///
250 /// #[monoio::main]
251 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
252 /// let file = OpenOptions::new()
253 /// .write(true)
254 /// .create(true)
255 /// .open("foo.txt")
256 /// .await?;
257 /// Ok(())
258 /// }
259 /// ```
260 pub fn create(&mut self, create: bool) -> &mut OpenOptions {
261 self.create = create;
262 self
263 }
264
265 /// Sets the option to create a new file, failing if it already exists.
266 ///
267 /// No file is allowed to exist at the target location, also no (dangling)
268 /// symlink. In this way, if the call succeeds, the file returned is
269 /// guaranteed to be new.
270 ///
271 /// This option is useful because it is atomic. Otherwise between checking
272 /// whether a file exists and creating a new one, the file may have been
273 /// created by another process (a TOCTOU race condition / attack).
274 ///
275 /// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are
276 /// ignored.
277 ///
278 /// The file must be opened with write or append access in order to create
279 /// a new file.
280 ///
281 /// [`.create()`]: OpenOptions::create
282 /// [`.truncate()`]: OpenOptions::truncate
283 ///
284 /// # Examples
285 ///
286 /// ```no_run
287 /// use monoio::fs::OpenOptions;
288 ///
289 /// #[monoio::main]
290 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
291 /// let file = OpenOptions::new()
292 /// .write(true)
293 /// .create_new(true)
294 /// .open("foo.txt")
295 /// .await?;
296 /// Ok(())
297 /// }
298 /// ```
299 pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
300 self.create_new = create_new;
301 self
302 }
303
304 /// Opens a file at `path` with the options specified by `self`.
305 ///
306 /// # Errors
307 ///
308 /// This function will return an error under a number of different
309 /// circumstances. Some of these error conditions are listed here, together
310 /// with their [`io::ErrorKind`]. The mapping to [`io::ErrorKind`]s is not
311 /// part of the compatibility contract of the function, especially the
312 /// [`Other`] kind might change to more specific kinds in the future.
313 ///
314 /// * [`NotFound`]: The specified file does not exist and neither `create` or `create_new` is
315 /// set.
316 /// * [`NotFound`]: One of the directory components of the file path does not exist.
317 /// * [`PermissionDenied`]: The user lacks permission to get the specified access rights for the
318 /// file.
319 /// * [`PermissionDenied`]: The user lacks permission to open one of the directory components of
320 /// the specified path.
321 /// * [`AlreadyExists`]: `create_new` was specified and the file already exists.
322 /// * [`InvalidInput`]: Invalid combinations of open options (truncate without write access, no
323 /// access mode set, etc.).
324 /// * [`Other`]: One of the directory components of the specified file path was not, in fact, a
325 /// directory.
326 /// * [`Other`]: Filesystem-level errors: full disk, write permission requested on a read-only
327 /// file system, exceeded disk quota, too many open files, too long filename, too many
328 /// symbolic links in the specified path (Unix-like systems only), etc.
329 ///
330 /// # Examples
331 ///
332 /// ```no_run
333 /// use monoio::fs::OpenOptions;
334 ///
335 /// #[monoio::main]
336 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
337 /// let file = OpenOptions::new().read(true).open("foo.txt").await?;
338 /// Ok(())
339 /// }
340 /// ```
341 ///
342 /// [`AlreadyExists`]: io::ErrorKind::AlreadyExists
343 /// [`InvalidInput`]: io::ErrorKind::InvalidInput
344 /// [`NotFound`]: io::ErrorKind::NotFound
345 /// [`Other`]: io::ErrorKind::Other
346 /// [`PermissionDenied`]: io::ErrorKind::PermissionDenied
347 pub async fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
348 let op = Op::open(path.as_ref(), self)?;
349
350 // Await the completion of the event
351 let completion = op.await;
352
353 // The file is open
354 Ok(File::from_shared_fd(SharedFd::new_without_register(
355 completion.meta.result?.into_inner() as _,
356 )))
357 }
358
359 #[cfg(unix)]
360 pub(crate) fn access_mode(&self) -> io::Result<libc::c_int> {
361 match (self.read, self.write, self.append) {
362 (true, false, false) => Ok(libc::O_RDONLY),
363 (false, true, false) => Ok(libc::O_WRONLY),
364 (true, true, false) => Ok(libc::O_RDWR),
365 (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND),
366 (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND),
367 (false, false, false) => Err(io::Error::from_raw_os_error(libc::EINVAL)),
368 }
369 }
370
371 #[cfg(windows)]
372 pub(crate) fn access_mode(&self) -> io::Result<u32> {
373 match (self.read, self.write, self.append, self.access_mode) {
374 (.., Some(mode)) => Ok(mode),
375 (true, false, false, None) => Ok(GENERIC_READ),
376 (false, true, false, None) => Ok(GENERIC_WRITE),
377 (true, true, false, None) => Ok(GENERIC_READ | GENERIC_WRITE),
378 (false, _, true, None) => Ok(FILE_GENERIC_WRITE & !FILE_WRITE_DATA),
379 (true, _, true, None) => Ok(GENERIC_READ | (FILE_GENERIC_WRITE & !FILE_WRITE_DATA)),
380 (false, false, false, None) => {
381 Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER as _))
382 }
383 }
384 }
385
386 #[cfg(unix)]
387 pub(crate) fn creation_mode(&self) -> io::Result<libc::c_int> {
388 match (self.write, self.append) {
389 (true, false) => {}
390 (false, false) => {
391 if self.truncate || self.create || self.create_new {
392 return Err(io::Error::from_raw_os_error(libc::EINVAL));
393 }
394 }
395 (_, true) => {
396 if self.truncate && !self.create_new {
397 return Err(io::Error::from_raw_os_error(libc::EINVAL));
398 }
399 }
400 }
401
402 Ok(match (self.create, self.truncate, self.create_new) {
403 (false, false, false) => 0,
404 (true, false, false) => libc::O_CREAT,
405 (false, true, false) => libc::O_TRUNC,
406 (true, true, false) => libc::O_CREAT | libc::O_TRUNC,
407 (_, _, true) => libc::O_CREAT | libc::O_EXCL,
408 })
409 }
410
411 #[cfg(windows)]
412 pub(crate) fn creation_mode(&self) -> io::Result<u32> {
413 match (self.write, self.append) {
414 (true, false) => {}
415 (false, false) => {
416 if self.truncate || self.create || self.create_new {
417 return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER as _));
418 }
419 }
420 (_, true) => {
421 if self.truncate && !self.create_new {
422 return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER as _));
423 }
424 }
425 }
426
427 Ok(match (self.create, self.truncate, self.create_new) {
428 (false, false, false) => OPEN_EXISTING,
429 (true, false, false) => OPEN_ALWAYS,
430 (false, true, false) => TRUNCATE_EXISTING,
431 (true, true, false) => CREATE_ALWAYS,
432 (_, _, true) => CREATE_NEW,
433 })
434 }
435
436 #[cfg(windows)]
437 pub(crate) fn get_flags_and_attributes(&self) -> u32 {
438 self.custom_flags
439 | self.attributes
440 | self.security_qos_flags
441 | if self.create_new {
442 FILE_FLAG_OPEN_REPARSE_POINT as _
443 } else {
444 0
445 }
446 }
447}
448
449#[cfg(unix)]
450impl OpenOptionsExt for OpenOptions {
451 fn mode(&mut self, mode: u32) -> &mut Self {
452 self.mode = mode as libc::mode_t;
453 self
454 }
455
456 fn custom_flags(&mut self, flags: i32) -> &mut Self {
457 self.custom_flags = flags as libc::c_int;
458 self
459 }
460}