rsfs_tokio/disk/
mod.rs

1//! A zero cost wrapper around [`std::fs`].
2//!
3//! The [`FS`] struct is an empty struct. All methods on it use `std::fs` functions. The intent of
4//! this module is to set the filesystem you use to `rsfs::disk::FS` in `main.rs` and to set the
5//! filesystem to `rsfs::mem::test::FS` (once it exists) in your tests.
6//!
7//! [`std::fs`]: https://doc.rust-lang.org/std/fs/
8//! [`FS`]: struct.FS.html
9//!
10//! # Examples
11//!
12//! ```
13//! # async fn foo() -> std::io::Result<()> {
14//! use rsfs::*;
15//! use rsfs::unix_ext::*;
16//!
17//! let fs = rsfs::disk::FS;
18//!
19//! let meta = fs.metadata("/").await?;
20//! assert!(meta.is_dir());
21//! assert_eq!(meta.permissions().mode(), 0o755);
22//! # Ok(())
23//! # }
24//! ```
25
26use std::ffi::OsString;
27use std::io::{Result, SeekFrom};
28use std::os::unix::fs::FileExt;
29use std::os::unix::prelude::{MetadataExt, PermissionsExt};
30use std::path::{Path, PathBuf};
31use std::pin::Pin;
32use std::time::SystemTime;
33
34use nix::unistd::{Gid, Uid};
35use pin_utils::unsafe_pinned;
36use tokio::fs as rs_fs;
37use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
38use tokio_stream::Stream;
39
40use crate::fs;
41
42#[cfg(unix)]
43use crate::unix_ext;
44
45/// A builder used to create directories in various manners.
46///
47/// This builder is a single element tuple containing a [`std::fs::DirBuilder`] that implements [`rsfs::DirBuilder`] and supports [unix extensions].
48///
49/// [`std::fs::DirBuilder`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html
50/// [`rsfs::DirBuilder`]: ../trait.DirBuilder.html
51/// [unix extensions]: ../unix_ext/trait.DirBuilderExt.html
52///
53/// # Examples
54///
55/// ```
56/// # use rsfs::*;
57/// # async fn foo() -> std::io::Result<()> {
58/// let fs = rsfs::disk::FS;
59/// let db = fs.new_dirbuilder();
60/// db.create("dir").await?;
61/// # Ok(())
62/// # }
63/// ```
64#[derive(Debug)]
65pub struct DirBuilder(rs_fs::DirBuilder);
66
67#[async_trait::async_trait]
68impl fs::DirBuilder for DirBuilder {
69    fn recursive(&mut self, recursive: bool) -> &mut Self {
70        self.0.recursive(recursive);
71        self
72    }
73    async fn create<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
74        self.0.create(path).await
75    }
76}
77
78#[cfg(unix)]
79impl unix_ext::DirBuilderExt for DirBuilder {
80    fn mode(&mut self, mode: u32) -> &mut Self {
81        self.0.mode(mode);
82        self
83    }
84}
85
86/// Entries returned by the [`ReadDir`] iterator.
87///
88/// An instance of `DirEntry` implements [`rsfs::DirEntry`] and represents an entry inside a
89/// directory on the in-memory filesystem. This struct is a single element tuple containing a
90/// [`std::fs::DirEntry`].
91///
92/// [`ReadDir`]: struct.ReadDir.html
93/// [`rsfs::DirEntry`]: ../trait.DirEntry.html
94/// [`std::fs::DirEntry`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html
95///
96/// # Examples
97///
98/// ```
99/// # use rsfs::*;
100/// # async fn foo() -> std::io::Result<()> {
101/// use tokio_stream::StreamExt;
102///
103/// let fs = rsfs::disk::FS;
104/// let mut read_dir = fs.read_dir(".").await?;
105///
106/// while let Some(entry) = read_dir.next().await {
107///     let entry = entry?;
108///     if let Some(entry) = entry {
109///         println!("{:?}: {:?}", entry.path(), entry.metadata().await?.permissions());
110///     }
111/// }
112/// # Ok(())
113/// # }
114/// ```
115#[derive(Debug)]
116pub struct DirEntry(rs_fs::DirEntry);
117
118#[async_trait::async_trait]
119impl fs::DirEntry for DirEntry {
120    type Metadata = Metadata;
121    type FileType = FileType;
122
123    fn path(&self) -> PathBuf {
124        self.0.path()
125    }
126    async fn metadata(&self) -> Result<Self::Metadata> {
127        self.0.metadata().await.map(Metadata)
128    }
129    async fn file_type(&self) -> Result<Self::FileType> {
130        self.0.file_type().await.map(FileType)
131    }
132    fn file_name(&self) -> OsString {
133        self.0.file_name()
134    }
135}
136
137/// Returned from [`Metadata::file_type`], this structure represents the type of a file.
138///
139/// This structure is a single element tuple containing a [`std::fs::FileType`] that implements [`rsfs::FileType`].
140///
141/// [`Metadata::file_type`]: ../trait.Metadata.html#tymethod.file_type
142/// [`std::fs::FileType`]: https://doc.rust-lang.org/std/fs/struct.FileType.html
143/// [`rsfs::FileType`]: ../trait.FileType.html
144///
145/// # Examples
146///
147/// ```
148/// # use rsfs::*;
149/// # async fn foo() -> std::io::Result<()> {
150/// let fs = rsfs::disk::FS;
151/// let f = fs.create_file("f").await?;
152/// assert!(fs.metadata("f").await?.file_type().is_file());
153/// # Ok(())
154/// # }
155/// ```
156#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
157pub struct FileType(std::fs::FileType);
158
159impl fs::FileType for FileType {
160    fn is_dir(&self) -> bool {
161        self.0.is_dir()
162    }
163    fn is_file(&self) -> bool {
164        self.0.is_file()
165    }
166    fn is_symlink(&self) -> bool {
167        self.0.is_symlink()
168    }
169}
170
171/// A view into a file on the filesystem.
172///
173/// An instance of `File` can be read or written to depending on the options it was opened with.
174/// Files also implement `Seek` to alter the logical cursor position of the internal file.
175///
176/// This struct is a single element tuple containing a [`std::fs::File`] that implements
177/// [`rsfs::File`] and has [unix extensions].
178///
179/// [`std::fs::File`]: https://doc.rust-lang.org/std/fs/struct.File.html
180/// [`rsfs::File`]: ../trait.File.html
181/// [unix extensions]: ../unix_ext/trait.FileExt.html
182///
183/// # Examples
184///
185/// ```
186/// # use rsfs::*;
187/// # use tokio::io::AsyncWriteExt;
188/// # async fn foo() -> std::io::Result<()> {
189/// let fs = rsfs::disk::FS;
190/// let mut f = fs.create_file("f").await?;
191/// assert_eq!(f.write(&[1, 2, 3]).await?, 3);
192/// # Ok(())
193/// # }
194/// ```
195#[repr(transparent)]
196#[derive(Debug)]
197pub struct File {
198    file: rs_fs::File,
199}
200
201impl File {
202    unsafe_pinned!(file: rs_fs::File);
203}
204
205#[async_trait::async_trait]
206impl fs::File for File {
207    type Metadata = Metadata;
208    type Permissions = Permissions;
209
210    async fn sync_all(&self) -> Result<()> {
211        self.file.sync_all().await
212    }
213    async fn sync_data(&self) -> Result<()> {
214        self.file.sync_data().await
215    }
216    async fn set_len(&self, size: u64) -> Result<()> {
217        self.file.set_len(size).await
218    }
219    async fn metadata(&self) -> Result<Self::Metadata> {
220        self.file.metadata().await.map(Metadata)
221    }
222    async fn try_clone(&self) -> Result<Self> {
223        self.file.try_clone().await.map(|file| File { file })
224    }
225    async fn set_permissions(&self, perm: Self::Permissions) -> Result<()> {
226        self.file.set_permissions(perm.0).await
227    }
228}
229
230impl AsyncRead for File {
231    fn poll_read(
232        self: std::pin::Pin<&mut Self>,
233        cx: &mut std::task::Context<'_>,
234        buf: &mut tokio::io::ReadBuf<'_>,
235    ) -> std::task::Poll<std::io::Result<()>> {
236        let file: Pin<&mut rs_fs::File> = self.file();
237        file.poll_read(cx, buf)
238    }
239}
240impl AsyncWrite for File {
241    fn poll_write(
242        self: std::pin::Pin<&mut Self>,
243        cx: &mut std::task::Context<'_>,
244        buf: &[u8],
245    ) -> std::task::Poll<std::result::Result<usize, std::io::Error>> {
246        let file: Pin<&mut rs_fs::File> = self.file();
247        file.poll_write(cx, buf)
248    }
249
250    fn poll_flush(
251        self: std::pin::Pin<&mut Self>,
252        cx: &mut std::task::Context<'_>,
253    ) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
254        let file: Pin<&mut rs_fs::File> = self.file();
255        file.poll_flush(cx)
256    }
257
258    fn poll_shutdown(
259        self: std::pin::Pin<&mut Self>,
260        cx: &mut std::task::Context<'_>,
261    ) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
262        let file: Pin<&mut rs_fs::File> = self.file();
263        file.poll_shutdown(cx)
264    }
265}
266impl AsyncSeek for File {
267    fn start_seek(self: std::pin::Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
268        let file: Pin<&mut rs_fs::File> = self.file();
269        file.start_seek(position)
270    }
271
272    fn poll_complete(
273        self: std::pin::Pin<&mut Self>,
274        cx: &mut std::task::Context<'_>,
275    ) -> std::task::Poll<std::io::Result<u64>> {
276        let file: Pin<&mut rs_fs::File> = self.file();
277        file.poll_complete(cx)
278    }
279}
280
281// TODO: Figure out how to implement this right
282// impl<'a> AsyncRead for &'a File {
283//     fn poll_read(
284//         self: std::pin::Pin<&mut Self>,
285//         cx: &mut std::task::Context<'_>,
286//         buf: &mut tokio::io::ReadBuf<'_>,
287//     ) -> std::task::Poll<std::io::Result<()>> {
288//         let file: Pin<&mut rs_fs::File> = self.file();
289//         file.poll_read(cx, buf)
290//     }
291// }
292// impl<'a> AsyncWrite for &'a File {
293//     fn poll_write(
294//         self: std::pin::Pin<&mut Self>,
295//         cx: &mut std::task::Context<'_>,
296//         buf: &[u8],
297//     ) -> std::task::Poll<std::result::Result<usize, std::io::Error>> {
298//         let file: Pin<&mut rs_fs::File> = self.file();
299//         file.poll_write(cx, buf)
300//     }
301
302//     fn poll_flush(
303//         self: std::pin::Pin<&mut Self>,
304//         cx: &mut std::task::Context<'_>,
305//     ) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
306//         let file: Pin<&mut rs_fs::File> = self.file();
307//         file.poll_flush(cx)
308//     }
309
310//     fn poll_shutdown(
311//         self: std::pin::Pin<&mut Self>,
312//         cx: &mut std::task::Context<'_>,
313//     ) -> std::task::Poll<std::result::Result<(), std::io::Error>> {
314//         let file: Pin<&mut rs_fs::File> = self.file();
315//         file.poll_shutdown(cx)
316//     }
317// }
318// impl<'a> AsyncSeek for &'a File {
319//     fn start_seek(self: std::pin::Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
320//         let file: Pin<&mut rs_fs::File> = self.file();
321//         file.start_seek(position)
322//     }
323
324//     fn poll_complete(
325//         self: std::pin::Pin<&mut Self>,
326//         cx: &mut std::task::Context<'_>,
327//     ) -> std::task::Poll<std::io::Result<u64>> {
328//         let file: Pin<&mut rs_fs::File> = self.file();
329//         file.poll_complete(cx)
330//     }
331// }
332
333#[async_trait::async_trait]
334impl unix_ext::FileExt for File {
335    async fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
336        let file = self.file.try_clone().await?.into_std().await;
337        let join_handle = tokio::task::spawn_blocking(move || {
338            let mut buf = vec![];
339            let res = file.read_at(&mut buf, offset);
340            (res, buf)
341        });
342        let (res, out_buf) = join_handle.await?;
343        buf.copy_from_slice(&out_buf);
344        res
345    }
346    async fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize> {
347        let file = self.file.try_clone().await?.into_std().await;
348        let in_buf = buf.to_vec();
349        let join_handle = tokio::task::spawn_blocking(move || file.write_at(&in_buf, offset));
350        join_handle.await?
351    }
352}
353
354/// Metadata information about a file.
355///
356/// This structure, a single element tuple containing a [`std::fs::Metadata`] that implements
357/// [`rsfs::Metadata`], is returned from the [`metadata`] or [`symlink_metadata`] methods and
358/// represents known metadata information about a file at the instant in time this structure is
359/// instantiated.
360///
361/// [`std::fs::Metadata`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html
362/// [`rsfs::Metadata`]: ../trait.Metadata.html
363/// [`metadata`]: ../trait.GenFS.html#tymethod.metadata
364/// [`symlink_metadata`]: ../trait.GenFS.html#tymethod.symlink_metadata
365///
366/// # Examples
367///
368/// ```
369/// # use rsfs::*;
370/// # async fn foo() -> std::io::Result<()> {
371/// let fs = rsfs::disk::FS;
372/// fs.create_file("f").await?;
373/// println!("{:?}", fs.metadata("f").await?);
374/// # Ok(())
375/// # }
376#[derive(Clone, Debug)]
377pub struct Metadata(std::fs::Metadata);
378
379impl fs::Metadata for Metadata {
380    type Permissions = Permissions;
381    type FileType = FileType;
382
383    fn file_type(&self) -> Self::FileType {
384        FileType(self.0.file_type())
385    }
386    fn is_dir(&self) -> bool {
387        self.0.is_dir()
388    }
389    fn is_file(&self) -> bool {
390        self.0.is_file()
391    }
392    fn len(&self) -> u64 {
393        self.0.len()
394    }
395    fn permissions(&self) -> Self::Permissions {
396        Permissions(self.0.permissions())
397    }
398    fn modified(&self) -> Result<SystemTime> {
399        self.0.modified()
400    }
401    fn accessed(&self) -> Result<SystemTime> {
402        self.0.accessed()
403    }
404    fn created(&self) -> Result<SystemTime> {
405        self.0.created()
406    }
407    fn uid(&self) -> Result<u32> {
408        Ok(self.0.uid())
409    }
410    fn gid(&self) -> Result<u32> {
411        Ok(self.0.gid())
412    }
413}
414
415/// Options and flags which can be used to configure how a file is opened.
416///
417/// This builder, created from `GenFS`s [`new_openopts`], exposes the ability to configure how a
418/// [`File`] is opened and what operations are permitted on the open file. `GenFS`s [`open_file`]
419/// and [`create_file`] methods are aliases for commonly used options with this builder.
420///
421/// This builder is a single element tuple containing a [`std::fs::OpenOptions`] that implements
422/// [`rsfs::OpenOptions`] and supports [unix extensions].
423///
424/// [`new_openopts`]: ../trait.GenFS.html#tymethod.new_openopts
425/// [`open_file`]: ../trait.GenFS.html#tymethod.open_file
426/// [`create_file`]: ../trait.GenFS.html#tymethod.create_file
427/// [`std::fs::OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
428/// [`rsfs::OpenOptions`]: ../trait.OpenOptions.html
429/// [unix extensions]: ../unix_ext/trait.OpenOptionsExt.html
430///
431/// # Examples
432///
433/// Opening a file to read:
434///
435/// ```
436/// # use rsfs::*;
437/// # async fn foo() -> std::io::Result<()> {
438/// # let fs = rsfs::disk::FS;
439/// let f = fs.new_openopts()
440///           .read(true)
441///           .open("f")
442///           .await?;
443/// # Ok(())
444/// # }
445/// ```
446///
447/// Opening a file for both reading and writing, as well as creating it if it doesn't exist:
448///
449/// ```
450/// # use rsfs::*;
451/// # async fn foo() -> std::io::Result<()> {
452/// # let fs = rsfs::disk::FS;
453/// let mut f = fs.new_openopts()
454///               .read(true)
455///               .write(true)
456///               .create(true)
457///               .open("f")
458///               .await?;
459/// # Ok(())
460/// # }
461/// ```
462#[derive(Clone, Debug)]
463pub struct OpenOptions(rs_fs::OpenOptions);
464
465#[async_trait::async_trait]
466impl fs::OpenOptions for OpenOptions {
467    type File = File;
468
469    fn read(&mut self, read: bool) -> &mut Self {
470        self.0.read(read);
471        self
472    }
473    fn write(&mut self, write: bool) -> &mut Self {
474        self.0.write(write);
475        self
476    }
477    fn append(&mut self, append: bool) -> &mut Self {
478        self.0.append(append);
479        self
480    }
481    fn truncate(&mut self, truncate: bool) -> &mut Self {
482        self.0.truncate(truncate);
483        self
484    }
485    fn create(&mut self, create: bool) -> &mut Self {
486        self.0.create(create);
487        self
488    }
489    fn create_new(&mut self, create_new: bool) -> &mut Self {
490        self.0.create_new(create_new);
491        self
492    }
493    async fn open<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::File> {
494        self.0.open(path).await.map(|file| File { file })
495    }
496}
497
498#[cfg(unix)]
499impl unix_ext::OpenOptionsExt for OpenOptions {
500    fn mode(&mut self, mode: u32) -> &mut Self {
501        self.0.mode(mode);
502        self
503    }
504    fn custom_flags(&mut self, flags: i32) -> &mut Self {
505        self.0.custom_flags(flags);
506        self
507    }
508}
509
510/// Representation of the various permissions on a file.
511///
512/// This struct is a single element tuple containing a [`std::fs::Permissions`] that implements
513/// [`rsfs::Permissions`] and has [unix extensions].
514///
515/// [`std::fs::Permissions`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html
516/// [`rsfs::Permissions`]: ../trait.Permissions.html
517/// [unix extensions]: ../unix_ext/trait.PermissionsExt.html
518///
519/// # Examples
520///
521/// ```
522/// # use rsfs::*;
523/// # use rsfs::mem::FS;
524/// use rsfs::unix_ext::*;
525/// use rsfs::mem::Permissions;
526/// # async fn foo() -> std::io::Result<()> {
527/// # let fs = FS::new();
528/// # fs.create_file("foo.txt").await?;
529///
530/// fs.set_permissions("foo.txt", Permissions::from_mode(0o400)).await?;
531/// # Ok(())
532/// # }
533/// ```
534#[derive(Clone, PartialEq, Eq, Debug)]
535pub struct Permissions(std::fs::Permissions);
536
537impl fs::Permissions for Permissions {
538    fn readonly(&self) -> bool {
539        self.0.readonly()
540    }
541    fn set_readonly(&mut self, readonly: bool) {
542        self.0.set_readonly(readonly)
543    }
544}
545
546#[cfg(unix)]
547impl unix_ext::PermissionsExt for Permissions {
548    fn mode(&self) -> u32 {
549        self.0.mode()
550    }
551    fn set_mode(&mut self, mode: u32) {
552        self.0.set_mode(mode)
553    }
554    fn from_mode(mode: u32) -> Self {
555        Permissions(std::fs::Permissions::from_mode(mode))
556    }
557}
558
559/// Iterator over entries in a directory.
560///
561/// This is returned from the [`read_dir`] method of `GenFS` and yields instances of
562/// `io::Result<DirEntry>`. Through a [`DirEntry`], information about contents of a directory can
563/// be learned.
564///
565/// This struct is as ingle element tuple containing a [`std::fs::ReadDir`].
566///
567/// [`read_dir`]: struct.FS.html#method.read_dir
568/// [`DirEntry`]: struct.DirEntry.html
569/// [`std::fs::ReadDir`]: https://doc.rust-lang.org/std/fs/struct.ReadDir.html
570#[derive(Debug)]
571#[repr(transparent)]
572pub struct ReadDir {
573    read_dir: rs_fs::ReadDir,
574}
575
576impl ReadDir {
577    unsafe_pinned!(read_dir: rs_fs::ReadDir);
578}
579
580impl Stream for ReadDir {
581    type Item = std::result::Result<Option<DirEntry>, std::io::Error>;
582
583    fn poll_next(
584        self: std::pin::Pin<&mut Self>,
585        cx: &mut std::task::Context<'_>,
586    ) -> std::task::Poll<Option<Self::Item>> {
587        let mut read_dir = self.read_dir();
588        read_dir
589            .poll_next_entry(cx)
590            .map(|r| Some(r.map(|o| o.map(DirEntry))))
591    }
592}
593
594/// An empty struct that satisfies [`rsfs::FS`] by calling [`std::fs`] functions.
595///
596/// Because this is an empty struct, it is inherently thread safe and copyable. The power of using
597/// `rsfs` comes from the ability to choose what filesystem you want to use where: your main can
598/// use a disk backed filesystem, but your tests can use a test filesystem with injected errors.
599///
600/// Alternatively, the in-memory filesystem could suit your needs without forcing you to use disk.
601///
602/// [`rsfs::FS`]: ../trait.FS.html
603/// [`std::fs`]: https://doc.rust-lang.org/std/fs/
604///
605/// # Examples
606///
607/// ```
608/// use rsfs::*;
609///
610/// let fs = rsfs::disk::FS;
611/// ```
612#[derive(Copy, Clone, Debug)]
613pub struct FS;
614
615#[async_trait::async_trait]
616impl fs::GenFS for FS {
617    type DirBuilder = DirBuilder;
618    type DirEntry = DirEntry;
619    type File = File;
620    type Metadata = Metadata;
621    type OpenOptions = OpenOptions;
622    type Permissions = Permissions;
623    type ReadDir = ReadDir;
624
625    async fn canonicalize<P: AsRef<Path> + Send>(&self, path: P) -> Result<PathBuf> {
626        rs_fs::canonicalize(path).await
627    }
628    async fn copy<P: AsRef<Path> + Send, Q: AsRef<Path> + Send>(
629        &self,
630        from: P,
631        to: Q,
632    ) -> Result<u64> {
633        rs_fs::copy(from, to).await
634    }
635    async fn create_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
636        rs_fs::create_dir(path).await
637    }
638    async fn create_dir_all<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
639        rs_fs::create_dir_all(path).await
640    }
641    async fn hard_link<P: AsRef<Path> + Send, Q: AsRef<Path> + Send>(
642        &self,
643        src: P,
644        dst: Q,
645    ) -> Result<()> {
646        rs_fs::hard_link(src, dst).await
647    }
648    async fn metadata<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::Metadata> {
649        rs_fs::metadata(path).await.map(Metadata)
650    }
651    async fn read_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::ReadDir> {
652        rs_fs::read_dir(path)
653            .await
654            .map(|read_dir| ReadDir { read_dir })
655    }
656    async fn read_link<P: AsRef<Path> + Send>(&self, path: P) -> Result<PathBuf> {
657        rs_fs::read_link(path).await
658    }
659    async fn remove_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
660        rs_fs::remove_dir(path).await
661    }
662    async fn remove_dir_all<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
663        rs_fs::remove_dir_all(path).await
664    }
665    async fn remove_file<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
666        rs_fs::remove_file(path).await
667    }
668    async fn rename<P: AsRef<Path> + Send, Q: AsRef<Path> + Send>(
669        &self,
670        from: P,
671        to: Q,
672    ) -> Result<()> {
673        rs_fs::rename(from, to).await
674    }
675    async fn set_permissions<P: AsRef<Path> + Send>(
676        &self,
677        path: P,
678        perm: Self::Permissions,
679    ) -> Result<()> {
680        rs_fs::set_permissions(path, perm.0).await
681    }
682    async fn symlink_metadata<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::Metadata> {
683        rs_fs::symlink_metadata(path).await.map(Metadata)
684    }
685    fn new_openopts(&self) -> Self::OpenOptions {
686        OpenOptions(rs_fs::OpenOptions::new())
687    }
688    fn new_dirbuilder(&self) -> Self::DirBuilder {
689        DirBuilder(rs_fs::DirBuilder::new())
690    }
691    async fn open_file<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::File> {
692        rs_fs::File::open(path).await.map(|file| File { file })
693    }
694    async fn create_file<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::File> {
695        rs_fs::File::create(path).await.map(|file| File { file })
696    }
697}
698
699#[cfg(unix)]
700#[async_trait::async_trait]
701impl unix_ext::GenFSExt for FS {
702    async fn symlink<P: AsRef<Path> + Send, Q: AsRef<Path> + Send>(
703        &self,
704        src: P,
705        dst: Q,
706    ) -> Result<()> {
707        tokio::fs::symlink(src, dst).await
708    }
709
710    async fn set_ownership<P: AsRef<Path> + Send>(
711        &self,
712        path: P,
713        uid: u32,
714        gid: u32,
715    ) -> Result<()> {
716        let path = path.as_ref().to_path_buf();
717        let join_handle = tokio::spawn(async move {
718            nix::unistd::chown(&path, Some(Uid::from(uid)), Some(Gid::from(gid)))
719        });
720        join_handle.await??;
721        Ok(())
722    }
723}