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}