1use std::ffi::OsString;
2use std::fs::{FileType, Metadata, Permissions};
3use std::os::unix::prelude::PermissionsExt;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use std::time::SystemTime;
7
8use tokio::fs::{DirBuilder, DirEntry, File, OpenOptions, ReadDir};
9use tokio::io::ReadBuf;
10use tracing::debug;
11
12use crate::*;
13
14#[derive(Default, Debug)]
15pub struct TokioFloppyDisk {
16 scope: Option<PathBuf>,
17}
18
19impl TokioFloppyDisk {
20 pub fn new(scope: Option<PathBuf>) -> Self {
21 Self { scope }
22 }
23}
24
25macro_rules! scoped {
26 ( $this: expr, $x:ident ) => {
27 let $x = if let Some(ref scope) = $this.scope {
28 let path: &Path = $x.as_ref();
29 if path.starts_with(scope) {
30 path.to_path_buf()
31 } else {
32 let path = path.strip_prefix("/").unwrap_or(&path).to_path_buf();
33 scope.join(path)
34 }
35 } else {
36 let path: &Path = $x.as_ref();
37 path.to_path_buf()
38 };
39 };
40}
41
42#[async_trait::async_trait]
43impl<'a> FloppyDisk<'a> for TokioFloppyDisk {
44 type DirBuilder = TokioDirBuilder;
45 type DirEntry = TokioDirEntry;
46 type File = TokioFile;
47 type FileType = TokioFileType;
48 type Metadata = TokioMetadata;
49 type OpenOptions = TokioOpenOptions;
50 type Permissions = TokioPermissions;
51 type ReadDir = TokioReadDir;
52
53 async fn canonicalize<P: AsRef<Path> + Send>(&self, path: P) -> Result<PathBuf> {
54 scoped!(self, path);
55 debug!(
56 "canonicalise {} (scope = {:?})",
57 path.display(),
58 &self.scope
59 );
60 tokio::fs::canonicalize(path).await
61 }
62
63 async fn copy<P: AsRef<Path> + Send>(&self, from: P, to: P) -> Result<u64> {
64 scoped!(self, from);
65 scoped!(self, to);
66 debug!(
67 "copy {} -> {} (scope = {:?})",
68 from.display(),
69 to.display(),
70 &self.scope
71 );
72 tokio::fs::copy(from, to).await
73 }
74
75 async fn create_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
76 scoped!(self, path);
77 debug!("create_dir {} (scope = {:?})", path.display(), &self.scope);
78 tokio::fs::create_dir(path).await
79 }
80
81 async fn create_dir_all<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
82 scoped!(self, path);
83 debug!(
84 "create_dir_all {} (scope = {:?})",
85 path.display(),
86 &self.scope
87 );
88 tokio::fs::create_dir_all(path).await
89 }
90
91 async fn hard_link<P: AsRef<Path> + Send>(&self, src: P, dst: P) -> Result<()> {
92 scoped!(self, src);
93 scoped!(self, dst);
94 debug!(
95 "hard_link {} -> {} (scope = {:?})",
96 src.display(),
97 dst.display(),
98 &self.scope
99 );
100 tokio::fs::hard_link(src, dst).await
101 }
102
103 async fn metadata<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::Metadata> {
104 scoped!(self, path);
105 debug!("metadata {} (scope = {:?})", path.display(), &self.scope);
106 tokio::fs::metadata(path).await.map(TokioMetadata)
107 }
108
109 async fn read<P: AsRef<Path> + Send>(&self, path: P) -> Result<Vec<u8>> {
110 scoped!(self, path);
111 debug!("read {} (scope = {:?})", path.display(), &self.scope);
112 tokio::fs::read(path).await
113 }
114
115 async fn read_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::ReadDir> {
116 scoped!(self, path);
117 debug!("read_dir {} (scope = {:?})", path.display(), &self.scope);
118 tokio::fs::read_dir(path).await.map(TokioReadDir)
119 }
120
121 async fn read_link<P: AsRef<Path> + Send>(&self, path: P) -> Result<PathBuf> {
122 scoped!(self, path);
123 debug!("read_link {} (scope = {:?})", path.display(), &self.scope);
124 tokio::fs::read_link(path).await
125 }
126
127 async fn read_to_string<P: AsRef<Path> + Send>(&self, path: P) -> Result<String> {
128 scoped!(self, path);
129 debug!(
130 "read_to_string {} (scope = {:?})",
131 path.display(),
132 &self.scope
133 );
134 tokio::fs::read_to_string(path).await
135 }
136
137 async fn remove_dir<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
138 scoped!(self, path);
139 debug!("remove_dir {} (scope = {:?})", path.display(), &self.scope);
140 tokio::fs::remove_dir(path).await
141 }
142
143 async fn remove_dir_all<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
144 scoped!(self, path);
145 debug!(
146 "remove_dir_all {} (scope = {:?})",
147 path.display(),
148 &self.scope
149 );
150 tokio::fs::remove_dir_all(path).await
151 }
152
153 async fn remove_file<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
154 scoped!(self, path);
155 debug!("remove_file {} (scope = {:?})", path.display(), &self.scope);
156 tokio::fs::remove_file(path).await
157 }
158
159 async fn rename<P: AsRef<Path> + Send>(&self, from: P, to: P) -> Result<()> {
160 scoped!(self, from);
161 scoped!(self, to);
162 debug!(
163 "rename {} -> {} (scope = {:?})",
164 from.display(),
165 to.display(),
166 &self.scope
167 );
168 tokio::fs::rename(from, to).await
169 }
170
171 async fn set_permissions<P: AsRef<Path> + Send>(
172 &self,
173 path: P,
174 perm: Self::Permissions,
175 ) -> Result<()> {
176 scoped!(self, path);
177 debug!(
178 "set_permissions {} (scope = {:?})",
179 path.display(),
180 &self.scope
181 );
182 tokio::fs::set_permissions(path, perm.0).await
183 }
184
185 async fn symlink<P: AsRef<Path> + Send>(&self, src: P, dst: P) -> Result<()> {
186 scoped!(self, src);
187 scoped!(self, dst);
188 debug!(
189 "symlink {} -> {} (scope = {:?})",
190 src.display(),
191 dst.display(),
192 &self.scope
193 );
194 tokio::fs::symlink(src, dst).await
195 }
196
197 async fn symlink_metadata<P: AsRef<Path> + Send>(&self, path: P) -> Result<Self::Metadata> {
198 scoped!(self, path);
199 debug!(
200 "symlink_metadata {} (scope = {:?})",
201 path.display(),
202 &self.scope
203 );
204 tokio::fs::symlink_metadata(path).await.map(TokioMetadata)
205 }
206
207 async fn try_exists<P: AsRef<Path> + Send>(&self, path: P) -> Result<bool> {
208 scoped!(self, path);
209 debug!("try_exists {} (scope = {:?})", path.display(), &self.scope);
210 tokio::fs::try_exists(path).await
211 }
212
213 async fn write<P: AsRef<Path> + Send>(
214 &self,
215 path: P,
216 contents: impl AsRef<[u8]> + Send,
217 ) -> Result<()> {
218 scoped!(self, path);
219 debug!("write {} (scope = {:?})", path.display(), &self.scope);
220 tokio::fs::write(path, contents).await
221 }
222
223 fn new_dir_builder(&'a self) -> Self::DirBuilder {
224 TokioDirBuilder(DirBuilder::new())
225 }
226}
227
228#[cfg(unix)]
229#[async_trait::async_trait]
230impl FloppyDiskUnixExt for TokioFloppyDisk {
231 async fn chown<P: Into<PathBuf> + Send>(&self, path: P, uid: u32, gid: u32) -> Result<()> {
232 let path = path.into();
233 scoped!(self, path);
234 debug!("chown {} (scope = {:?})", path.display(), &self.scope);
235
236 tokio::task::spawn_blocking(move || {
237 use std::os::unix::prelude::OsStrExt;
238
239 unsafe {
241 libc::chown(
242 path.as_os_str().as_bytes().as_ptr() as *const libc::c_char,
243 uid,
244 gid,
245 );
246 }
247 Ok(())
248 })
249 .await?
250 }
251}
252
253#[repr(transparent)]
254#[derive(Debug)]
255pub struct TokioMetadata(#[doc(hidden)] Metadata);
256
257#[async_trait::async_trait]
258impl<'a> FloppyMetadata<'a, TokioFloppyDisk> for TokioMetadata {
259 fn file_type(&self) -> <TokioFloppyDisk as FloppyDisk<'a>>::FileType {
260 TokioFileType(self.0.file_type())
261 }
262
263 fn is_dir(&self) -> bool {
264 self.0.is_dir()
265 }
266
267 fn is_file(&self) -> bool {
268 self.0.is_file()
269 }
270
271 fn is_symlink(&self) -> bool {
272 self.0.is_symlink()
273 }
274
275 fn len(&self) -> u64 {
276 self.0.len()
277 }
278
279 fn permissions(&self) -> <TokioFloppyDisk as FloppyDisk<'a>>::Permissions {
280 TokioPermissions(self.0.permissions())
281 }
282
283 fn modified(&self) -> Result<SystemTime> {
284 self.0.modified()
285 }
286
287 fn accessed(&self) -> Result<SystemTime> {
288 self.0.accessed()
289 }
290
291 fn created(&self) -> Result<SystemTime> {
292 self.0.created()
293 }
294}
295
296#[cfg(unix)]
297impl FloppyUnixMetadata for TokioMetadata {
298 fn uid(&self) -> Result<u32> {
299 use std::os::unix::prelude::MetadataExt;
300 Ok(self.0.uid())
301 }
302
303 fn gid(&self) -> Result<u32> {
304 use std::os::unix::prelude::MetadataExt;
305 Ok(self.0.gid())
306 }
307}
308
309#[repr(transparent)]
310#[derive(Debug)]
311pub struct TokioReadDir(#[doc(hidden)] ReadDir);
312
313#[async_trait::async_trait]
314impl<'a> FloppyReadDir<'a, TokioFloppyDisk> for TokioReadDir {
315 async fn next_entry(
316 &mut self,
317 ) -> Result<Option<<TokioFloppyDisk as FloppyDisk<'a>>::DirEntry>> {
318 self.0
319 .next_entry()
320 .await
321 .map(|entry| entry.map(TokioDirEntry))
322 }
323}
324
325#[repr(transparent)]
326#[derive(Debug)]
327pub struct TokioPermissions(#[doc(hidden)] Permissions);
328
329impl FloppyPermissions for TokioPermissions {
330 fn readonly(&self) -> bool {
331 self.0.readonly()
332 }
333
334 fn set_readonly(&mut self, readonly: bool) {
335 self.0.set_readonly(readonly)
336 }
337}
338
339#[cfg(unix)]
340impl FloppyUnixPermissions for TokioPermissions {
341 fn mode(&self) -> u32 {
342 self.0.mode()
343 }
344
345 fn set_mode(&mut self, mode: u32) {
346 self.0.set_mode(mode)
347 }
348
349 fn from_mode(mode: u32) -> Self {
350 Self(Permissions::from_mode(mode))
351 }
352}
353
354#[repr(transparent)]
355#[derive(Debug)]
356pub struct TokioDirBuilder(#[doc(hidden)] DirBuilder);
357
358#[async_trait::async_trait]
359impl FloppyDirBuilder for TokioDirBuilder {
360 fn recursive(&mut self, recursive: bool) -> &mut Self {
361 self.0.recursive(recursive);
362 self
363 }
364
365 async fn create<P: AsRef<Path> + Send>(&self, path: P) -> Result<()> {
366 self.0.create(path).await
367 }
368
369 fn mode(&mut self, mode: u32) -> &mut Self {
370 self.0.mode(mode);
371 self
372 }
373}
374
375#[repr(transparent)]
376#[derive(Debug)]
377pub struct TokioDirEntry(#[doc(hidden)] DirEntry);
378
379#[async_trait::async_trait]
380impl<'a> FloppyDirEntry<'a, TokioFloppyDisk> for TokioDirEntry {
381 fn file_name(&self) -> OsString {
382 self.0.file_name()
383 }
384
385 async fn file_type(&self) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::FileType> {
386 self.0.file_type().await.map(TokioFileType)
387 }
388
389 async fn metadata(&self) -> Result<TokioMetadata> {
390 self.0.metadata().await.map(TokioMetadata)
391 }
392
393 fn path(&self) -> PathBuf {
394 self.0.path()
395 }
396
397 #[cfg(unix)]
398 fn ino(&self) -> u64 {
399 self.0.ino()
400 }
401}
402
403#[repr(transparent)]
404#[derive(Debug)]
405pub struct TokioFileType(#[doc(hidden)] FileType);
406
407impl FloppyFileType for TokioFileType {
408 fn is_dir(&self) -> bool {
409 self.0.is_dir()
410 }
411
412 fn is_file(&self) -> bool {
413 self.0.is_file()
414 }
415
416 fn is_symlink(&self) -> bool {
417 self.0.is_symlink()
418 }
419}
420
421#[derive(Debug)]
422pub struct TokioOpenOptions(#[doc(hidden)] OpenOptions);
423
424#[async_trait::async_trait]
425impl<'a> FloppyOpenOptions<'a, TokioFloppyDisk> for TokioOpenOptions {
426 fn new() -> Self {
427 Self(OpenOptions::new())
428 }
429
430 fn read(self, read: bool) -> Self {
431 let mut oo = self.0;
432 oo.read(read);
433 Self(oo)
434 }
435
436 fn write(self, write: bool) -> Self {
437 let mut oo = self.0;
438 oo.write(write);
439 Self(oo)
440 }
441
442 fn append(self, append: bool) -> Self {
443 let mut oo = self.0;
444 oo.append(append);
445 Self(oo)
446 }
447
448 fn truncate(self, truncate: bool) -> Self {
449 let mut oo = self.0;
450 oo.truncate(truncate);
451 Self(oo)
452 }
453
454 fn create(self, create: bool) -> Self {
455 let mut oo = self.0;
456 oo.create(create);
457 Self(oo)
458 }
459
460 fn create_new(self, create_new: bool) -> Self {
461 let mut oo = self.0;
462 oo.create_new(create_new);
463 Self(oo)
464 }
465
466 async fn open<P: AsRef<Path> + Send>(
467 &self,
468 disk: &'a TokioFloppyDisk,
469 path: P,
470 ) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::File> {
471 let path = if let Some(ref scope) = disk.scope {
473 let path: &Path = path.as_ref();
474 let path = path.strip_prefix("/").unwrap_or(path).to_path_buf();
475 scope.join(path)
476 } else {
477 path.as_ref().to_path_buf()
478 };
479 debug!("opening {}", path.display());
480 self.0.open(path).await.map(TokioFile)
481 }
482}
483
484#[derive(Debug)]
485#[repr(transparent)]
486pub struct TokioFile(#[doc(hidden)] File);
487
488#[async_trait::async_trait]
489impl<'a> FloppyFile<'a, TokioFloppyDisk> for TokioFile {
490 async fn sync_all(&mut self) -> Result<()> {
491 self.0.sync_all().await
492 }
493
494 async fn sync_data(&mut self) -> Result<()> {
495 self.0.sync_data().await
496 }
497
498 async fn set_len(&mut self, size: u64) -> Result<()> {
499 self.0.set_len(size).await
500 }
501
502 async fn metadata(&self) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::Metadata> {
503 self.0.metadata().await.map(TokioMetadata)
504 }
505
506 async fn try_clone(&'a self) -> Result<Box<Self>> {
507 self.0
508 .try_clone()
509 .await
510 .map(|file| Box::new(TokioFile(file)))
511 }
512
513 async fn set_permissions(
514 &self,
515 perm: <TokioFloppyDisk as FloppyDisk>::Permissions,
516 ) -> Result<()> {
517 self.0.set_permissions(perm.0).await
518 }
519
520 async fn permissions(&self) -> Result<<TokioFloppyDisk as FloppyDisk<'a>>::Permissions> {
521 self.0
522 .metadata()
523 .await
524 .map(|metadata| TokioPermissions(metadata.permissions()))
525 }
526}
527
528impl AsyncRead for TokioFile {
529 fn poll_read(
530 self: Pin<&mut Self>,
531 cx: &mut Context<'_>,
532 buf: &mut ReadBuf<'_>,
533 ) -> Poll<Result<()>> {
534 Pin::new(&mut self.get_mut().0).poll_read(cx, buf)
535 }
536}
537
538impl AsyncSeek for TokioFile {
539 fn start_seek(self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> {
540 Pin::new(&mut self.get_mut().0).start_seek(position)
541 }
542
543 fn poll_complete(
544 self: Pin<&mut Self>,
545 cx: &mut std::task::Context<'_>,
546 ) -> std::task::Poll<std::io::Result<u64>> {
547 Pin::new(&mut self.get_mut().0).poll_complete(cx)
548 }
549}
550
551impl AsyncWrite for TokioFile {
552 fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
553 Pin::new(&mut self.get_mut().0).poll_write(cx, buf)
554 }
555
556 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
557 Pin::new(&mut self.get_mut().0).poll_flush(cx)
558 }
559
560 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
561 Pin::new(&mut self.get_mut().0).poll_shutdown(cx)
562 }
563}
564
565#[cfg(test)]
615mod tests {
616 use super::*;
617
618 #[tokio::test]
619 async fn test_scoping_works() -> std::io::Result<()> {
620 let fs = TokioFloppyDisk::new(Some(PathBuf::from("/tmp")));
621 fs.write("/a", "asdf").await?;
622 let out = fs.read_to_string("/a").await?;
623
624 assert_eq!("asdf", out);
625 assert!(tokio::fs::metadata("/tmp/a").await.is_ok());
626
627 fs.remove_file("/a").await?;
628
629 Ok(())
630 }
631}