zng_task/fs.rs
1//! Async filesystem primitives.
2//!
3//! This module is the [async-fs](https://docs.rs/async-fs) crate re-exported for convenience.
4//!
5
6#[doc(inline)]
7pub use async_fs::*;
8
9//
10// Module contains patched version of File
11// TODO(breaking) replace with reexport again after https://github.com/smol-rs/async-fs/pull/55 is released
12//
13
14use std::fmt;
15use std::future::Future;
16use std::io::{self, SeekFrom};
17use std::path::Path;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::task::{Context, Poll};
21
22#[cfg(unix)]
23use std::os::unix::fs::OpenOptionsExt as _;
24
25#[cfg(windows)]
26use std::os::windows::fs::OpenOptionsExt as _;
27
28use async_lock::Mutex;
29use blocking::{Unblock, unblock};
30use futures_lite::io::{AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt};
31use futures_lite::ready;
32
33#[doc(no_inline)]
34pub use std::fs::{FileType, Metadata, Permissions};
35
36/// An open file on the filesystem.
37///
38/// Depending on what options the file was opened with, this type can be used for reading and/or
39/// writing.
40///
41/// Files are automatically closed when they get dropped and any errors detected on closing are
42/// ignored. Use the [`sync_all()`][`File::sync_all()`] method before dropping a file if such
43/// errors need to be handled.
44///
45/// **NOTE:** If writing to a file, make sure to call
46/// [`flush()`][`futures_lite::io::AsyncWriteExt::flush()`], [`sync_data()`][`File::sync_data()`],
47/// or [`sync_all()`][`File::sync_all()`] before dropping the file or else some written data
48/// might get lost!
49///
50/// # Examples
51///
52/// Create a new file and write some bytes to it:
53///
54/// ```no_run
55/// use futures_lite::io::AsyncWriteExt;
56/// use zng_task::fs::File;
57///
58/// # futures_lite::future::block_on(async {
59/// let mut file = File::create("a.txt").await?;
60///
61/// file.write_all(b"Hello, world!").await?;
62/// file.flush().await?;
63/// # std::io::Result::Ok(()) });
64/// ```
65///
66/// Read the contents of a file into a vector of bytes:
67///
68/// ```no_run
69/// use futures_lite::io::AsyncReadExt;
70/// use zng_task::fs::File;
71///
72/// # futures_lite::future::block_on(async {
73/// let mut file = File::open("a.txt").await?;
74///
75/// let mut contents = Vec::new();
76/// file.read_to_end(&mut contents).await?;
77/// # std::io::Result::Ok(()) });
78/// ```
79pub struct File {
80 /// Always accessible reference to the file.
81 file: Arc<std::fs::File>,
82
83 /// Performs blocking I/O operations on a thread pool.
84 unblock: Mutex<Unblock<ArcFile>>,
85
86 /// Logical file cursor, tracked when reading from the file.
87 ///
88 /// This will be set to an error if the file is not seekable.
89 read_pos: Option<io::Result<u64>>,
90
91 /// Set to `true` if the file needs flushing.
92 is_dirty: bool,
93}
94
95impl File {
96 /// Creates an async file from a blocking file.
97 fn new(inner: std::fs::File, is_dirty: bool) -> File {
98 let file = Arc::new(inner);
99 let unblock = Mutex::new(Unblock::new(ArcFile(file.clone())));
100 let read_pos = None;
101 File {
102 file,
103 unblock,
104 read_pos,
105 is_dirty,
106 }
107 }
108
109 /// Opens a file in read-only mode.
110 ///
111 /// See the [`OpenOptions::open()`] function for more options.
112 ///
113 /// # Errors
114 ///
115 /// An error will be returned in the following situations:
116 ///
117 /// * `path` does not point to an existing file.
118 /// * The current process lacks permissions to read the file.
119 /// * Some other I/O error occurred.
120 ///
121 /// For more details, see the list of errors documented by [`OpenOptions::open()`].
122 ///
123 /// # Examples
124 ///
125 /// ```no_run
126 /// use zng_task::fs::File;
127 ///
128 /// # futures_lite::future::block_on(async {
129 /// let file = File::open("a.txt").await?;
130 /// # std::io::Result::Ok(()) });
131 /// ```
132 pub async fn open<P: AsRef<Path>>(path: P) -> io::Result<File> {
133 let path = path.as_ref().to_owned();
134 let file = unblock(move || std::fs::File::open(path)).await?;
135 Ok(File::new(file, false))
136 }
137
138 /// Opens a file in write-only mode.
139 ///
140 /// This method will create a file if it does not exist, and will truncate it if it does.
141 ///
142 /// See the [`OpenOptions::open`] function for more options.
143 ///
144 /// # Errors
145 ///
146 /// An error will be returned in the following situations:
147 ///
148 /// * The file's parent directory does not exist.
149 /// * The current process lacks permissions to write to the file.
150 /// * Some other I/O error occurred.
151 ///
152 /// For more details, see the list of errors documented by [`OpenOptions::open()`].
153 ///
154 /// # Examples
155 ///
156 /// ```no_run
157 /// use zng_task::fs::File;
158 ///
159 /// # futures_lite::future::block_on(async {
160 /// let file = File::create("a.txt").await?;
161 /// # std::io::Result::Ok(()) });
162 /// ```
163 pub async fn create<P: AsRef<Path>>(path: P) -> io::Result<File> {
164 let path = path.as_ref().to_owned();
165 let file = unblock(move || std::fs::File::create(path)).await?;
166 Ok(File::new(file, false))
167 }
168
169 /// Synchronizes OS-internal buffered contents and metadata to disk.
170 ///
171 /// This function will ensure that all in-memory data reaches the filesystem.
172 ///
173 /// This can be used to handle errors that would otherwise only be caught by closing the file.
174 /// When a file is dropped, errors in synchronizing this in-memory data are ignored.
175 ///
176 /// # Examples
177 ///
178 /// ```no_run
179 /// use futures_lite::io::AsyncWriteExt;
180 /// use zng_task::fs::File;
181 ///
182 /// # futures_lite::future::block_on(async {
183 /// let mut file = File::create("a.txt").await?;
184 ///
185 /// file.write_all(b"Hello, world!").await?;
186 /// file.sync_all().await?;
187 /// # std::io::Result::Ok(()) });
188 /// ```
189 pub async fn sync_all(&self) -> io::Result<()> {
190 let mut inner = self.unblock.lock().await;
191 inner.flush().await?;
192 let file = self.file.clone();
193 unblock(move || file.sync_all()).await
194 }
195
196 /// Synchronizes OS-internal buffered contents to disk.
197 ///
198 /// This is similar to [`sync_all()`][`File::sync_all()`], except that file metadata may not
199 /// be synchronized.
200 ///
201 /// This is intended for use cases that must synchronize the contents of the file, but don't
202 /// need the file metadata synchronized to disk.
203 ///
204 /// Note that some platforms may simply implement this in terms of
205 /// [`sync_all()`][`File::sync_all()`].
206 ///
207 /// # Examples
208 ///
209 /// ```no_run
210 /// use futures_lite::io::AsyncWriteExt;
211 /// use zng_task::fs::File;
212 ///
213 /// # futures_lite::future::block_on(async {
214 /// let mut file = File::create("a.txt").await?;
215 ///
216 /// file.write_all(b"Hello, world!").await?;
217 /// file.sync_data().await?;
218 /// # std::io::Result::Ok(()) });
219 /// ```
220 pub async fn sync_data(&self) -> io::Result<()> {
221 let mut inner = self.unblock.lock().await;
222 inner.flush().await?;
223 let file = self.file.clone();
224 unblock(move || file.sync_data()).await
225 }
226
227 /// Truncates or extends the file.
228 ///
229 /// If `size` is less than the current file size, then the file will be truncated. If it is
230 /// greater than the current file size, then the file will be extended to `size` and have all
231 /// intermediate data filled with zeros.
232 ///
233 /// The file's cursor stays at the same position, even if the cursor ends up being past the end
234 /// of the file after this operation.
235 ///
236 /// # Examples
237 ///
238 /// ```no_run
239 /// use zng_task::fs::File;
240 ///
241 /// # futures_lite::future::block_on(async {
242 /// let mut file = File::create("a.txt").await?;
243 /// file.set_len(10).await?;
244 /// # std::io::Result::Ok(()) });
245 /// ```
246 pub async fn set_len(&self, size: u64) -> io::Result<()> {
247 let mut inner = self.unblock.lock().await;
248 inner.flush().await?;
249 let file = self.file.clone();
250 unblock(move || file.set_len(size)).await
251 }
252
253 /// Reads the file's metadata.
254 ///
255 /// # Examples
256 ///
257 /// ```no_run
258 /// use zng_task::fs::File;
259 ///
260 /// # futures_lite::future::block_on(async {
261 /// let file = File::open("a.txt").await?;
262 /// let metadata = file.metadata().await?;
263 /// # std::io::Result::Ok(()) });
264 /// ```
265 pub async fn metadata(&self) -> io::Result<Metadata> {
266 let file = self.file.clone();
267 unblock(move || file.metadata()).await
268 }
269
270 /// Changes the permissions on the file.
271 ///
272 /// # Errors
273 ///
274 /// An error will be returned in the following situations:
275 ///
276 /// * The current process lacks permissions to change attributes on the file.
277 /// * Some other I/O error occurred.
278 ///
279 /// # Examples
280 ///
281 /// ```no_run
282 /// use zng_task::fs::File;
283 ///
284 /// # futures_lite::future::block_on(async {
285 /// let file = File::create("a.txt").await?;
286 ///
287 /// let mut perms = file.metadata().await?.permissions();
288 /// perms.set_readonly(true);
289 /// file.set_permissions(perms).await?;
290 /// # std::io::Result::Ok(()) });
291 /// ```
292 pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
293 let file = self.file.clone();
294 unblock(move || file.set_permissions(perm)).await
295 }
296
297 /// Repositions the cursor after reading.
298 ///
299 /// When reading from a file, actual file reads run asynchronously in the background, which
300 /// means the real file cursor is usually ahead of the logical cursor, and the data between
301 /// them is buffered in memory. This kind of buffering is an important optimization.
302 ///
303 /// After reading ends, if we decide to perform a write or a seek operation, the real file
304 /// cursor must first be repositioned back to the correct logical position.
305 fn poll_reposition(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
306 if let Some(Ok(read_pos)) = self.read_pos {
307 ready!(Pin::new(self.unblock.get_mut()).poll_seek(cx, SeekFrom::Start(read_pos)))?;
308 }
309 self.read_pos = None;
310 Poll::Ready(Ok(()))
311 }
312
313 /// Returns the inner blocking file, if no task is running.
314 ///
315 /// This will flush any pending data I/O tasks before attempting to unwrap, it will fail
316 /// if there are pending metadata tasks. Note that dropping futures does not cancel file
317 /// tasks, you must await all pending futures for this conversion to succeed.
318 pub async fn try_unwrap(self) -> Result<std::fs::File, Self> {
319 // flush Unblock and drop its reference
320 let _ = self.unblock.into_inner().into_inner().await;
321
322 match Arc::try_unwrap(self.file) {
323 Ok(ready) => Ok(ready),
324 Err(pending) => {
325 // task associated with dropped future is still running
326 Err(Self {
327 file: pending.clone(),
328 unblock: Mutex::new(Unblock::new(ArcFile(pending))),
329 is_dirty: false,
330 read_pos: self.read_pos,
331 })
332 }
333 }
334 }
335}
336
337impl fmt::Debug for File {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 self.file.fmt(f)
340 }
341}
342
343impl From<std::fs::File> for File {
344 fn from(inner: std::fs::File) -> File {
345 File::new(inner, true)
346 }
347}
348
349#[cfg(unix)]
350impl std::os::unix::io::AsRawFd for File {
351 fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
352 self.file.as_raw_fd()
353 }
354}
355
356#[cfg(windows)]
357impl std::os::windows::io::AsRawHandle for File {
358 fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
359 self.file.as_raw_handle()
360 }
361}
362
363#[cfg(unix)]
364impl From<std::os::unix::io::OwnedFd> for File {
365 fn from(fd: std::os::unix::io::OwnedFd) -> Self {
366 File::from(std::fs::File::from(fd))
367 }
368}
369
370#[cfg(windows)]
371impl From<std::os::windows::io::OwnedHandle> for File {
372 fn from(fd: std::os::windows::io::OwnedHandle) -> Self {
373 File::from(std::fs::File::from(fd))
374 }
375}
376
377#[cfg(unix)]
378impl std::os::unix::io::AsFd for File {
379 fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> {
380 self.file.as_fd()
381 }
382}
383
384#[cfg(windows)]
385impl std::os::windows::io::AsHandle for File {
386 fn as_handle(&self) -> std::os::windows::io::BorrowedHandle<'_> {
387 self.file.as_handle()
388 }
389}
390
391impl AsyncRead for File {
392 fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<io::Result<usize>> {
393 // Before reading begins, remember the current cursor position.
394 if self.read_pos.is_none() {
395 // Initialize the logical cursor to the current position in the file.
396 self.read_pos = Some(ready!(self.as_mut().poll_seek(cx, SeekFrom::Current(0))));
397 }
398
399 let n = ready!(Pin::new(self.unblock.get_mut()).poll_read(cx, buf))?;
400
401 // Update the logical cursor if the file is seekable.
402 if let Some(Ok(pos)) = self.read_pos.as_mut() {
403 *pos += n as u64;
404 }
405
406 Poll::Ready(Ok(n))
407 }
408}
409
410impl AsyncWrite for File {
411 fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
412 ready!(self.poll_reposition(cx))?;
413 self.is_dirty = true;
414 Pin::new(self.unblock.get_mut()).poll_write(cx, buf)
415 }
416
417 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
418 if self.is_dirty {
419 ready!(Pin::new(self.unblock.get_mut()).poll_flush(cx))?;
420 self.is_dirty = false;
421 }
422 Poll::Ready(Ok(()))
423 }
424
425 fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
426 Pin::new(self.unblock.get_mut()).poll_close(cx)
427 }
428}
429
430impl AsyncSeek for File {
431 fn poll_seek(mut self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom) -> Poll<io::Result<u64>> {
432 ready!(self.poll_reposition(cx))?;
433 Pin::new(self.unblock.get_mut()).poll_seek(cx, pos)
434 }
435}
436
437/// A wrapper around `Arc<std::fs::File>` that implements `Read`, `Write`, and `Seek`.
438struct ArcFile(Arc<std::fs::File>);
439
440impl io::Read for ArcFile {
441 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
442 (&*self.0).read(buf)
443 }
444}
445
446impl io::Write for ArcFile {
447 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
448 (&*self.0).write(buf)
449 }
450
451 fn flush(&mut self) -> io::Result<()> {
452 (&*self.0).flush()
453 }
454}
455
456impl io::Seek for ArcFile {
457 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
458 (&*self.0).seek(pos)
459 }
460}
461
462/// A builder for opening files with configurable options.
463///
464/// Files can be opened in [`read`][`OpenOptions::read()`] and/or
465/// [`write`][`OpenOptions::write()`] mode.
466///
467/// The [`append`][`OpenOptions::append()`] option opens files in a special writing mode that
468/// moves the file cursor to the end of file before every write operation.
469///
470/// It is also possible to [`truncate`][`OpenOptions::truncate()`] the file right after opening,
471/// to [`create`][`OpenOptions::create()`] a file if it doesn't exist yet, or to always create a
472/// new file with [`create_new`][`OpenOptions::create_new()`].
473///
474/// # Examples
475///
476/// Open a file for reading:
477///
478/// ```no_run
479/// use zng_task::fs::OpenOptions;
480///
481/// # futures_lite::future::block_on(async {
482/// let file = OpenOptions::new().read(true).open("a.txt").await?;
483/// # std::io::Result::Ok(()) });
484/// ```
485///
486/// Open a file for both reading and writing, and create it if it doesn't exist yet:
487///
488/// ```no_run
489/// use zng_task::fs::OpenOptions;
490///
491/// # futures_lite::future::block_on(async {
492/// let file = OpenOptions::new().read(true).write(true).create(true).open("a.txt").await?;
493/// # std::io::Result::Ok(()) });
494/// ```
495#[derive(Clone, Debug)]
496pub struct OpenOptions(std::fs::OpenOptions);
497
498impl OpenOptions {
499 /// Creates a blank set of options.
500 ///
501 /// All options are initially set to `false`.
502 ///
503 /// # Examples
504 ///
505 /// ```no_run
506 /// use zng_task::fs::OpenOptions;
507 ///
508 /// # futures_lite::future::block_on(async {
509 /// let file = OpenOptions::new().read(true).open("a.txt").await?;
510 /// # std::io::Result::Ok(()) });
511 /// ```
512 pub fn new() -> OpenOptions {
513 OpenOptions(std::fs::OpenOptions::new())
514 }
515
516 /// Configures the option for read mode.
517 ///
518 /// When set to `true`, this option means the file will be readable after opening.
519 ///
520 /// # Examples
521 ///
522 /// ```no_run
523 /// use zng_task::fs::OpenOptions;
524 ///
525 /// # futures_lite::future::block_on(async {
526 /// let file = OpenOptions::new().read(true).open("a.txt").await?;
527 /// # std::io::Result::Ok(()) });
528 /// ```
529 pub fn read(&mut self, read: bool) -> &mut OpenOptions {
530 self.0.read(read);
531 self
532 }
533
534 /// Configures the option for write mode.
535 ///
536 /// When set to `true`, this option means the file will be writable after opening.
537 ///
538 /// If the file already exists, write calls on it will overwrite the previous contents without
539 /// truncating it.
540 ///
541 /// # Examples
542 ///
543 /// ```no_run
544 /// use zng_task::fs::OpenOptions;
545 ///
546 /// # futures_lite::future::block_on(async {
547 /// let file = OpenOptions::new().write(true).open("a.txt").await?;
548 /// # std::io::Result::Ok(()) });
549 /// ```
550 pub fn write(&mut self, write: bool) -> &mut OpenOptions {
551 self.0.write(write);
552 self
553 }
554
555 /// Configures the option for append mode.
556 ///
557 /// When set to `true`, this option means the file will be writable after opening and the file
558 /// cursor will be moved to the end of file before every write operation.
559 ///
560 /// # Examples
561 ///
562 /// ```no_run
563 /// use zng_task::fs::OpenOptions;
564 ///
565 /// # futures_lite::future::block_on(async {
566 /// let file = OpenOptions::new().append(true).open("a.txt").await?;
567 /// # std::io::Result::Ok(()) });
568 /// ```
569 pub fn append(&mut self, append: bool) -> &mut OpenOptions {
570 self.0.append(append);
571 self
572 }
573
574 /// Configures the option for truncating the previous file.
575 ///
576 /// When set to `true`, the file will be truncated to the length of 0 bytes.
577 ///
578 /// The file must be opened in [`write`][`OpenOptions::write()`] or
579 /// [`append`][`OpenOptions::append()`] mode for truncation to work.
580 ///
581 /// # Examples
582 ///
583 /// ```no_run
584 /// use zng_task::fs::OpenOptions;
585 ///
586 /// # futures_lite::future::block_on(async {
587 /// let file = OpenOptions::new().write(true).truncate(true).open("a.txt").await?;
588 /// # std::io::Result::Ok(()) });
589 /// ```
590 pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions {
591 self.0.truncate(truncate);
592 self
593 }
594
595 /// Configures the option for creating a new file if it doesn't exist.
596 ///
597 /// When set to `true`, this option means a new file will be created if it doesn't exist.
598 ///
599 /// The file must be opened in [`write`][`OpenOptions::write()`] or
600 /// [`append`][`OpenOptions::append()`] mode for file creation to work.
601 ///
602 /// # Examples
603 ///
604 /// ```no_run
605 /// use zng_task::fs::OpenOptions;
606 ///
607 /// # futures_lite::future::block_on(async {
608 /// let file = OpenOptions::new().write(true).create(true).open("a.txt").await?;
609 /// # std::io::Result::Ok(()) });
610 /// ```
611 pub fn create(&mut self, create: bool) -> &mut OpenOptions {
612 self.0.create(create);
613 self
614 }
615
616 /// Configures the option for creating a new file or failing if it already exists.
617 ///
618 /// When set to `true`, this option means a new file will be created, or the open operation
619 /// will fail if the file already exists.
620 ///
621 /// The file must be opened in [`write`][`OpenOptions::write()`] or
622 /// [`append`][`OpenOptions::append()`] mode for file creation to work.
623 ///
624 /// # Examples
625 ///
626 /// ```no_run
627 /// use zng_task::fs::OpenOptions;
628 ///
629 /// # futures_lite::future::block_on(async {
630 /// let file = OpenOptions::new().write(true).create_new(true).open("a.txt").await?;
631 /// # std::io::Result::Ok(()) });
632 /// ```
633 pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
634 self.0.create_new(create_new);
635 self
636 }
637
638 /// Opens a file with the configured options.
639 ///
640 /// # Errors
641 ///
642 /// An error will be returned in the following situations:
643 ///
644 /// * The file does not exist and neither [`create`] nor [`create_new`] were set.
645 /// * The file's parent directory does not exist.
646 /// * The current process lacks permissions to open the file in the configured mode.
647 /// * The file already exists and [`create_new`] was set.
648 /// * Invalid combination of options was used, like [`truncate`] was set but [`write`] wasn't,
649 /// or none of [`read`], [`write`], and [`append`] modes was set.
650 /// * An OS-level occurred, like too many files are open or the file name is too long.
651 /// * Some other I/O error occurred.
652 ///
653 /// [`read`]: `OpenOptions::read()`
654 /// [`write`]: `OpenOptions::write()`
655 /// [`append`]: `OpenOptions::append()`
656 /// [`truncate`]: `OpenOptions::truncate()`
657 /// [`create`]: `OpenOptions::create()`
658 /// [`create_new`]: `OpenOptions::create_new()`
659 ///
660 /// # Examples
661 ///
662 /// ```no_run
663 /// use zng_task::fs::OpenOptions;
664 ///
665 /// # futures_lite::future::block_on(async {
666 /// let file = OpenOptions::new().read(true).open("a.txt").await?;
667 /// # std::io::Result::Ok(()) });
668 /// ```
669 pub fn open<P: AsRef<Path>>(&self, path: P) -> impl Future<Output = io::Result<File>> {
670 let path = path.as_ref().to_owned();
671 let options = self.0.clone();
672 async move {
673 let file = unblock(move || options.open(path)).await?;
674 Ok(File::new(file, false))
675 }
676 }
677}
678
679impl Default for OpenOptions {
680 fn default() -> Self {
681 Self::new()
682 }
683}
684
685#[cfg(unix)]
686impl unix::OpenOptionsExt for OpenOptions {
687 fn mode(&mut self, mode: u32) -> &mut Self {
688 self.0.mode(mode);
689 self
690 }
691
692 fn custom_flags(&mut self, flags: i32) -> &mut Self {
693 self.0.custom_flags(flags);
694 self
695 }
696}
697
698#[cfg(windows)]
699impl windows::OpenOptionsExt for OpenOptions {
700 fn access_mode(&mut self, access: u32) -> &mut Self {
701 self.0.access_mode(access);
702 self
703 }
704
705 fn share_mode(&mut self, val: u32) -> &mut Self {
706 self.0.share_mode(val);
707 self
708 }
709
710 fn custom_flags(&mut self, flags: u32) -> &mut Self {
711 self.0.custom_flags(flags);
712 self
713 }
714
715 fn attributes(&mut self, val: u32) -> &mut Self {
716 self.0.attributes(val);
717 self
718 }
719
720 fn security_qos_flags(&mut self, flags: u32) -> &mut Self {
721 self.0.security_qos_flags(flags);
722 self
723 }
724}
725
726#[cfg_attr(not(any(windows, unix)), allow(dead_code))]
727mod __private {
728 #[doc(hidden)]
729 pub trait Sealed {}
730
731 impl Sealed for super::OpenOptions {}
732 impl Sealed for super::File {}
733}
734
735/// Unix-specific extensions.
736#[cfg(unix)]
737pub mod unix {
738 use super::__private::Sealed;
739
740 #[doc(inline)]
741 pub use async_fs::unix::*;
742
743 /// Unix-specific extensions to [`OpenOptions`].
744 ///
745 /// [`OpenOptions`]: crate::fs::OpenOptions
746 pub trait OpenOptionsExt: Sealed {
747 /// Sets the mode bits that a new file will be created with.
748 ///
749 /// If a new file is created as part of an [`OpenOptions::open()`] call then this
750 /// specified `mode` will be used as the permission bits for the new file.
751 ///
752 /// If no `mode` is set, the default of `0o666` will be used.
753 /// The operating system masks out bits with the system's `umask`, to produce
754 /// the final permissions.
755 ///
756 /// [`OpenOptions::open()`]: crate::fs::OpenOptions::open
757 ///
758 /// # Examples
759 ///
760 /// ```no_run
761 /// use zng_task::fs::{OpenOptions, unix::OpenOptionsExt};
762 ///
763 /// # futures_lite::future::block_on(async {
764 /// let mut options = OpenOptions::new();
765 /// // Read/write permissions for owner and read permissions for others.
766 /// options.mode(0o644);
767 /// let file = options.open("foo.txt").await?;
768 /// # std::io::Result::Ok(()) });
769 /// ```
770 fn mode(&mut self, mode: u32) -> &mut Self;
771
772 /// Passes custom flags to the `flags` argument of `open`.
773 ///
774 /// The bits that define the access mode are masked out with `O_ACCMODE`, to
775 /// ensure they do not interfere with the access mode set by Rust's options.
776 ///
777 /// Custom flags can only set flags, not remove flags set by Rust's options.
778 /// This options overwrites any previously set custom flags.
779 ///
780 /// # Examples
781 ///
782 /// ```no_run
783 /// # mod libc { pub const O_NOFOLLOW: i32 = 0x40000; }
784 /// use zng_task::fs::{OpenOptions, unix::OpenOptionsExt};
785 ///
786 /// # futures_lite::future::block_on(async {
787 /// let mut options = OpenOptions::new();
788 /// options.write(true);
789 /// options.custom_flags(libc::O_NOFOLLOW);
790 /// let file = options.open("foo.txt").await?;
791 /// # std::io::Result::Ok(()) });
792 /// ```
793 fn custom_flags(&mut self, flags: i32) -> &mut Self;
794 }
795}
796
797/// Windows-specific extensions.
798#[cfg(windows)]
799pub mod windows {
800 use super::__private::Sealed;
801
802 #[doc(inline)]
803 pub use async_fs::windows::*;
804
805 /// Windows-specific extensions to [`OpenOptions`].
806 ///
807 /// [`OpenOptions`]: crate::fs::OpenOptions
808 pub trait OpenOptionsExt: Sealed {
809 /// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`]
810 /// with the specified value.
811 ///
812 /// This will override the `read`, `write`, and `append` flags on the
813 /// [`OpenOptions`] structure. This method provides fine-grained control over
814 /// the permissions to read, write and append data, attributes (like hidden
815 /// and system), and extended attributes.
816 ///
817 /// # Examples
818 ///
819 /// ```no_run
820 /// use zng_task::fs::{OpenOptions, windows::OpenOptionsExt};
821 ///
822 /// # futures_lite::future::block_on(async {
823 /// // Open without read and write permission, for example if you only need
824 /// // to call `stat` on the file
825 /// let file = OpenOptions::new().access_mode(0).open("foo.txt").await?;
826 /// # std::io::Result::Ok(()) });
827 /// ```
828 ///
829 /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
830 /// [`OpenOptions`]: crate::fs::OpenOptions
831 fn access_mode(&mut self, access: u32) -> &mut Self;
832
833 /// Overrides the `dwShareMode` argument to the call to [`CreateFile`] with
834 /// the specified value.
835 ///
836 /// By default `share_mode` is set to
837 /// `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE`. This allows
838 /// other processes to read, write, and delete/rename the same file
839 /// while it is open. Removing any of the flags will prevent other
840 /// processes from performing the corresponding operation until the file
841 /// handle is closed.
842 ///
843 /// # Examples
844 ///
845 /// ```no_run
846 /// use zng_task::fs::{OpenOptions, windows::OpenOptionsExt};
847 ///
848 /// # futures_lite::future::block_on(async {
849 /// // Do not allow others to read or modify this file while we have it open
850 /// // for writing.
851 /// let file = OpenOptions::new().write(true).share_mode(0).open("foo.txt").await?;
852 /// # std::io::Result::Ok(()) });
853 /// ```
854 ///
855 /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
856 fn share_mode(&mut self, val: u32) -> &mut Self;
857
858 /// Sets extra flags for the `dwFileFlags` argument to the call to
859 /// [`CreateFile2`] to the specified value (or combines it with
860 /// `attributes` and `security_qos_flags` to set the `dwFlagsAndAttributes`
861 /// for [`CreateFile`]).
862 ///
863 /// Custom flags can only set flags, not remove flags set by Rust's options.
864 /// This option overwrites any previously set custom flags.
865 ///
866 /// # Examples
867 ///
868 /// ```no_run
869 /// use zng_task::fs::{OpenOptions, windows::OpenOptionsExt};
870 ///
871 /// # futures_lite::future::block_on(async {
872 /// let file = OpenOptions::new()
873 /// .create(true)
874 /// .write(true)
875 /// .custom_flags(windows_sys::Win32::Storage::FileSystem::FILE_FLAG_DELETE_ON_CLOSE)
876 /// .open("foo.txt")
877 /// .await?;
878 /// # std::io::Result::Ok(()) });
879 /// ```
880 ///
881 /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
882 /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
883 fn custom_flags(&mut self, flags: u32) -> &mut Self;
884
885 /// Sets the `dwFileAttributes` argument to the call to [`CreateFile2`] to
886 /// the specified value (or combines it with `custom_flags` and
887 /// `security_qos_flags` to set the `dwFlagsAndAttributes` for
888 /// [`CreateFile`]).
889 ///
890 /// If a _new_ file is created because it does not yet exist and
891 /// `.create(true)` or `.create_new(true)` are specified, the new file is
892 /// given the attributes declared with `.attributes()`.
893 ///
894 /// If an _existing_ file is opened with `.create(true).truncate(true)`, its
895 /// existing attributes are preserved and combined with the ones declared
896 /// with `.attributes()`.
897 ///
898 /// In all other cases the attributes get ignored.
899 ///
900 /// # Examples
901 ///
902 /// ```no_run
903 /// use zng_task::fs::{OpenOptions, windows::OpenOptionsExt};
904 ///
905 /// # futures_lite::future::block_on(async {
906 /// let file = OpenOptions::new()
907 /// .write(true)
908 /// .create(true)
909 /// .attributes(windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_HIDDEN)
910 /// .open("foo.txt")
911 /// .await?;
912 /// # std::io::Result::Ok(()) });
913 /// ```
914 ///
915 /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
916 /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
917 fn attributes(&mut self, val: u32) -> &mut Self;
918
919 /// Sets the `dwSecurityQosFlags` argument to the call to [`CreateFile2`] to
920 /// the specified value (or combines it with `custom_flags` and `attributes`
921 /// to set the `dwFlagsAndAttributes` for [`CreateFile`]).
922 ///
923 /// By default `security_qos_flags` is not set. It should be specified when
924 /// opening a named pipe, to control to which degree a server process can
925 /// act on behalf of a client process (security impersonation level).
926 ///
927 /// When `security_qos_flags` is not set, a malicious program can gain the
928 /// elevated privileges of a privileged Rust process when it allows opening
929 /// user-specified paths, by tricking it into opening a named pipe. So
930 /// arguably `security_qos_flags` should also be set when opening arbitrary
931 /// paths. However the bits can then conflict with other flags, specifically
932 /// `FILE_FLAG_OPEN_NO_RECALL`.
933 ///
934 /// For information about possible values, see [Impersonation Levels] on the
935 /// Windows Dev Center site. The `SECURITY_SQOS_PRESENT` flag is set
936 /// automatically when using this method.
937 ///
938 /// # Examples
939 ///
940 /// ```no_run
941 /// use zng_task::fs::{OpenOptions, windows::OpenOptionsExt};
942 ///
943 /// # futures_lite::future::block_on(async {
944 /// let file = OpenOptions::new()
945 /// .write(true)
946 /// .create(true)
947 /// .security_qos_flags(windows_sys::Win32::Storage::FileSystem::SECURITY_IDENTIFICATION)
948 /// .open(r"\\.\pipe\MyPipe")
949 /// .await?;
950 /// # std::io::Result::Ok(()) });
951 /// ```
952 ///
953 /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
954 /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
955 /// [Impersonation Levels]: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
956 fn security_qos_flags(&mut self, flags: u32) -> &mut Self;
957 }
958}