virtual_fs/
host_fs.rs

1use crate::{
2    DirEntry, FileType, FsError, Metadata, OpenOptions, OpenOptionsConfig, ReadDir, Result,
3    VirtualFile,
4};
5use bytes::{Buf, Bytes};
6use futures::future::BoxFuture;
7use std::convert::TryInto;
8use std::fs;
9use std::io::{self, Seek};
10use std::path::{Component, Path, PathBuf};
11use std::pin::Pin;
12use std::sync::Arc;
13use std::task::{Context, Poll};
14use std::time::{SystemTime, UNIX_EPOCH};
15use tokio::fs as tfs;
16use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
17use tokio::runtime::Handle;
18
19#[derive(Debug, Clone)]
20pub struct FileSystem {
21    handle: Handle,
22    root: PathBuf,
23}
24
25#[allow(dead_code)]
26fn default_handle() -> Handle {
27    Handle::current()
28}
29
30pub fn canonicalize(path: &Path) -> Result<PathBuf> {
31    if !path.exists() {
32        return Err(FsError::InvalidInput);
33    }
34    dunce::canonicalize(path).map_err(Into::into)
35}
36
37// Copied from cargo
38// https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
39pub fn normalize_path(path: &Path) -> PathBuf {
40    let mut components = path.components().peekable();
41    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
42        components.next();
43        PathBuf::from(c.as_os_str())
44    } else {
45        PathBuf::new()
46    };
47
48    for component in components {
49        match component {
50            Component::Prefix(..) => unreachable!(),
51            Component::RootDir => {
52                ret.push(component.as_os_str());
53            }
54            Component::CurDir => {}
55            Component::ParentDir => {
56                ret.pop();
57            }
58            Component::Normal(c) => {
59                ret.push(c);
60            }
61        }
62    }
63    ret
64}
65
66fn path_suffix_to_guest_absolute(stripped: &Path) -> PathBuf {
67    let mut stripped = stripped.to_string_lossy().into_owned();
68    if std::path::MAIN_SEPARATOR == '\\' {
69        stripped = stripped.replace('\\', "/");
70    }
71
72    PathBuf::from(format!("/{}", stripped.trim_start_matches('/')))
73}
74
75fn strip_host_root(root: &Path, target: &Path) -> Option<PathBuf> {
76    target
77        .strip_prefix(root)
78        .ok()
79        .map(path_suffix_to_guest_absolute)
80}
81
82fn host_root_relative_target(root: &Path, target: PathBuf) -> PathBuf {
83    if root == Path::new("/") || !target.is_absolute() {
84        return target;
85    }
86
87    if let Some(target) = strip_host_root(root, &target) {
88        return target;
89    }
90
91    if let Ok(canonical_target) = canonicalize(&target)
92        && let Some(target) = strip_host_root(root, &canonical_target)
93    {
94        return target;
95    }
96
97    target
98}
99
100impl FileSystem {
101    pub fn new(handle: Handle, root: impl Into<PathBuf>) -> Result<Self> {
102        let root = canonicalize(&root.into())?;
103
104        Ok(FileSystem { handle, root })
105    }
106
107    fn prepare_path(&self, path: &Path) -> Result<PathBuf> {
108        let path = normalize_path(path);
109
110        if matches!(path.components().next(), Some(Component::Prefix(..))) {
111            return Err(FsError::InvalidInput);
112        }
113
114        if self.root != Path::new("/") && path.starts_with(&self.root) {
115            return Err(FsError::InvalidInput);
116        }
117
118        let path = path.strip_prefix("/").unwrap_or(&path);
119        let path = self.root.join(path);
120
121        debug_assert!(path.starts_with(&self.root));
122        Ok(path)
123    }
124}
125
126impl crate::FileSystem for FileSystem {
127    fn readlink(&self, path: &Path) -> Result<PathBuf> {
128        let path = self.prepare_path(path)?;
129
130        let target = fs::read_link(path)?;
131        Ok(host_root_relative_target(&self.root, target))
132    }
133
134    fn read_dir(&self, path: &Path) -> Result<ReadDir> {
135        let path = self.prepare_path(path)?;
136
137        let read_dir = fs::read_dir(path)?;
138        let mut data = read_dir
139            .map(|entry| {
140                let entry = entry?;
141
142                let path = entry
143                    .path()
144                    .strip_prefix(&self.root)
145                    .map_err(|_| FsError::InvalidData)?
146                    .to_owned();
147                let path = Path::new("/").join(path);
148
149                let metadata = fs::symlink_metadata(entry.path())?;
150
151                Ok(DirEntry {
152                    path,
153                    metadata: Ok(metadata.try_into()?),
154                })
155            })
156            .collect::<std::result::Result<Vec<DirEntry>, io::Error>>()
157            .map_err::<FsError, _>(Into::into)?;
158        data.sort_by(|a, b| a.path.file_name().cmp(&b.path.file_name()));
159        Ok(ReadDir::new(data))
160    }
161
162    fn create_dir(&self, path: &Path) -> Result<()> {
163        let path = self.prepare_path(path)?;
164
165        if path.parent().is_none() {
166            return Err(FsError::BaseNotDirectory);
167        }
168
169        fs::create_dir(path).map_err(Into::into)
170    }
171
172    fn remove_dir(&self, path: &Path) -> Result<()> {
173        let path = self.prepare_path(path)?;
174
175        if path.parent().is_none() {
176            return Err(FsError::BaseNotDirectory);
177        }
178
179        // https://github.com/rust-lang/rust/issues/86442
180        // DirectoryNotEmpty is not implemented consistently
181        if path.is_dir()
182            && fs::read_dir(&path)
183                .map(|mut s| s.next().is_some())
184                .unwrap_or(false)
185        {
186            return Err(FsError::DirectoryNotEmpty);
187        }
188        fs::remove_dir(path).map_err(Into::into)
189    }
190
191    fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
192        Box::pin(async move {
193            use filetime::{FileTime, set_file_mtime};
194            let norm_from = normalize_path(from);
195            let norm_to = normalize_path(to);
196
197            if norm_from.parent().is_none() {
198                return Err(FsError::BaseNotDirectory);
199            }
200            if norm_to.parent().is_none() {
201                return Err(FsError::BaseNotDirectory);
202            }
203
204            let from = self.prepare_path(from)?;
205            let to = self.prepare_path(to)?;
206
207            if !from.exists() {
208                return Err(FsError::EntryNotFound);
209            }
210            let from_parent = from.parent().unwrap();
211            let to_parent = to.parent().unwrap();
212            if !from_parent.exists() {
213                return Err(FsError::EntryNotFound);
214            }
215            if !to_parent.exists() {
216                return Err(FsError::EntryNotFound);
217            }
218            let result = if from_parent != to_parent {
219                let _ = std::fs::create_dir_all(to_parent);
220                if from.is_dir() {
221                    fs_extra::move_items(
222                        &[&from],
223                        &to,
224                        &fs_extra::dir::CopyOptions {
225                            copy_inside: true,
226                            ..Default::default()
227                        },
228                    )
229                    .map(|_| ())
230                    .map_err(|_| FsError::UnknownError)?;
231                    let _ = fs_extra::remove_items(&[&from]);
232                    Ok(())
233                } else {
234                    fs::copy(&from, &to).map(|_| ()).map_err(FsError::from)?;
235                    fs::remove_file(&from).map(|_| ()).map_err(Into::into)
236                }
237            } else {
238                fs::rename(&from, &to).map_err(Into::into)
239            };
240            let _ = set_file_mtime(&to, FileTime::now()).map(|_| ());
241            result
242        })
243    }
244
245    fn remove_file(&self, path: &Path) -> Result<()> {
246        let path = self.prepare_path(path)?;
247
248        if path.parent().is_none() {
249            return Err(FsError::BaseNotDirectory);
250        }
251
252        fs::remove_file(path).map_err(Into::into)
253    }
254
255    fn new_open_options(&self) -> OpenOptions<'_> {
256        OpenOptions::new(self)
257    }
258
259    fn metadata(&self, path: &Path) -> Result<Metadata> {
260        let path = self.prepare_path(path)?;
261
262        fs::metadata(path)
263            .and_then(TryInto::try_into)
264            .map_err(Into::into)
265    }
266
267    fn symlink_metadata(&self, path: &Path) -> Result<Metadata> {
268        let path = self.prepare_path(path)?;
269
270        fs::symlink_metadata(path)
271            .and_then(TryInto::try_into)
272            .map_err(Into::into)
273    }
274}
275
276impl TryInto<Metadata> for std::fs::Metadata {
277    type Error = io::Error;
278
279    fn try_into(self) -> std::result::Result<Metadata, Self::Error> {
280        let filetype = self.file_type();
281        let (char_device, block_device, socket, fifo) = {
282            #[cfg(unix)]
283            {
284                use std::os::unix::fs::FileTypeExt;
285                (
286                    filetype.is_char_device(),
287                    filetype.is_block_device(),
288                    filetype.is_socket(),
289                    filetype.is_fifo(),
290                )
291            }
292            #[cfg(not(unix))]
293            {
294                (false, false, false, false)
295            }
296        };
297
298        Ok(Metadata {
299            ft: FileType {
300                dir: filetype.is_dir(),
301                file: filetype.is_file(),
302                symlink: filetype.is_symlink(),
303                char_device,
304                block_device,
305                socket,
306                fifo,
307            },
308            accessed: self
309                .accessed()
310                .and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
311                .map_or(0, |time| time.as_nanos() as u64),
312            created: self
313                .created()
314                .and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
315                .map_or(0, |time| time.as_nanos() as u64),
316            modified: self
317                .modified()
318                .and_then(|time| time.duration_since(UNIX_EPOCH).map_err(io::Error::other))
319                .map_or(0, |time| time.as_nanos() as u64),
320            len: self.len(),
321        })
322    }
323}
324
325impl crate::FileOpener for FileSystem {
326    fn open(
327        &self,
328        path: &Path,
329        conf: &OpenOptionsConfig,
330    ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
331        let path = self.prepare_path(path)?;
332
333        // TODO: handle create implying write, etc.
334        let read = conf.read();
335        let write = conf.write();
336
337        // according to Rust's stdlib, specifying both truncate and append is nonsensical,
338        // and it will return an error if we try to open a file with both flags set.
339        // in order to prevent this, and stay compatible with native binaries, we just ignore
340        // the append flag if truncate is set. the rationale behind this decision is that
341        // truncate is going to be applied first and append is going to be ignored anyway.
342        let append = if conf.truncate { false } else { conf.append() };
343
344        let mut oo = fs::OpenOptions::new();
345        oo.read(conf.read())
346            .write(conf.write())
347            .create_new(conf.create_new())
348            .create(conf.create())
349            .append(append)
350            .truncate(conf.truncate())
351            .open(&path)
352            .map_err(Into::into)
353            .map(|file| {
354                Box::new(File::new(
355                    self.handle.clone(),
356                    file,
357                    path.to_owned(),
358                    read,
359                    write,
360                    append,
361                )) as Box<dyn VirtualFile + Send + Sync + 'static>
362            })
363    }
364}
365
366/// A thin wrapper around `std::fs::File`
367#[derive(Debug)]
368pub struct File {
369    handle: Handle,
370    inner: tfs::File,
371    inner_std: fs::File,
372    pub host_path: PathBuf,
373}
374
375impl File {
376    const READ: u16 = 1;
377    const WRITE: u16 = 2;
378    const APPEND: u16 = 4;
379
380    /// creates a new host file from a `std::fs::File` and a path
381    pub fn new(
382        handle: Handle,
383        file: fs::File,
384        host_path: PathBuf,
385        read: bool,
386        write: bool,
387        append: bool,
388    ) -> Self {
389        let mut _flags = 0;
390
391        if read {
392            _flags |= Self::READ;
393        }
394
395        if write {
396            _flags |= Self::WRITE;
397        }
398
399        if append {
400            _flags |= Self::APPEND;
401        }
402
403        let async_file = tfs::File::from_std(file.try_clone().unwrap());
404        Self {
405            handle,
406            inner_std: file,
407            inner: async_file,
408            host_path,
409        }
410    }
411
412    fn metadata(&self) -> std::fs::Metadata {
413        // FIXME: no unwrap!
414        self.inner_std.metadata().unwrap()
415    }
416}
417
418#[async_trait::async_trait]
419impl VirtualFile for File {
420    fn last_accessed(&self) -> u64 {
421        self.metadata()
422            .accessed()
423            .ok()
424            .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
425            .map(|ct| ct.as_nanos() as u64)
426            .unwrap_or(0)
427    }
428
429    fn last_modified(&self) -> u64 {
430        self.metadata()
431            .modified()
432            .ok()
433            .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
434            .map(|ct| ct.as_nanos() as u64)
435            .unwrap_or(0)
436    }
437
438    fn created_time(&self) -> u64 {
439        self.metadata()
440            .created()
441            .ok()
442            .and_then(|ct| ct.duration_since(SystemTime::UNIX_EPOCH).ok())
443            .map(|ct| ct.as_nanos() as u64)
444            .unwrap_or(0)
445    }
446
447    fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
448        let atime = atime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
449        let mtime = mtime.map(|t| filetime::FileTime::from_unix_time(t as i64, 0));
450
451        filetime::set_file_handle_times(&self.inner_std, atime, mtime)
452            .map_err(|_| crate::FsError::IOError)
453    }
454
455    fn size(&self) -> u64 {
456        self.metadata().len()
457    }
458
459    fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
460        fs::File::set_len(&self.inner_std, new_size).map_err(Into::into)
461    }
462
463    fn unlink(&mut self) -> Result<()> {
464        fs::remove_file(&self.host_path).map_err(Into::into)
465    }
466
467    fn get_special_fd(&self) -> Option<u32> {
468        None
469    }
470
471    fn poll_read_ready(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
472        let cursor = match self.inner_std.stream_position() {
473            Ok(a) => a,
474            Err(err) => return Poll::Ready(Err(err)),
475        };
476        let end = match self.inner_std.seek(io::SeekFrom::End(0)) {
477            Ok(a) => a,
478            Err(err) => return Poll::Ready(Err(err)),
479        };
480        let _ = self.inner_std.seek(io::SeekFrom::Start(cursor));
481
482        let remaining = end - cursor;
483        Poll::Ready(Ok(remaining as usize))
484    }
485
486    fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
487        Poll::Ready(Ok(8192))
488    }
489}
490
491impl AsyncRead for File {
492    fn poll_read(
493        mut self: Pin<&mut Self>,
494        cx: &mut Context<'_>,
495        buf: &mut tokio::io::ReadBuf<'_>,
496    ) -> Poll<io::Result<()>> {
497        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
498        let inner = Pin::new(&mut self.inner);
499        inner.poll_read(cx, buf)
500    }
501}
502
503impl AsyncWrite for File {
504    fn poll_write(
505        mut self: Pin<&mut Self>,
506        cx: &mut Context<'_>,
507        buf: &[u8],
508    ) -> Poll<io::Result<usize>> {
509        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
510        let inner = Pin::new(&mut self.inner);
511        inner.poll_write(cx, buf)
512    }
513
514    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
515        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
516        let inner = Pin::new(&mut self.inner);
517        inner.poll_flush(cx)
518    }
519
520    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
521        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
522        let inner = Pin::new(&mut self.inner);
523        inner.poll_shutdown(cx)
524    }
525
526    fn poll_write_vectored(
527        mut self: Pin<&mut Self>,
528        cx: &mut Context<'_>,
529        bufs: &[io::IoSlice<'_>],
530    ) -> Poll<io::Result<usize>> {
531        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
532        let inner = Pin::new(&mut self.inner);
533        inner.poll_write_vectored(cx, bufs)
534    }
535
536    fn is_write_vectored(&self) -> bool {
537        self.inner.is_write_vectored()
538    }
539}
540
541impl AsyncSeek for File {
542    fn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {
543        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
544        let inner = Pin::new(&mut self.inner);
545        inner.start_seek(position)
546    }
547
548    fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
549        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
550        let inner = Pin::new(&mut self.inner);
551        inner.poll_complete(cx)
552    }
553}
554
555impl Drop for File {
556    fn drop(&mut self) {
557        tracing::trace!(?self.host_path, "Closing host file");
558    }
559}
560
561/// A wrapper type around Stdout that implements `VirtualFile`.
562#[derive(Debug)]
563pub struct Stdout {
564    handle: Handle,
565    inner: tokio::io::Stdout,
566}
567#[allow(dead_code)]
568fn default_stdout() -> tokio::io::Stdout {
569    tokio::io::stdout()
570}
571impl Default for Stdout {
572    fn default() -> Self {
573        Self {
574            handle: Handle::current(),
575            inner: tokio::io::stdout(),
576        }
577    }
578}
579
580/// Default size for write buffers.
581///
582/// Chosen to be both sufficiently large, and a multiple of the default page
583/// size on most systems.
584///
585/// This value has limited meaning, since it is only used for buffer size hints,
586/// and those hints are often ignored.
587const DEFAULT_BUF_SIZE_HINT: usize = 8 * 1024;
588
589#[async_trait::async_trait]
590impl VirtualFile for Stdout {
591    fn last_accessed(&self) -> u64 {
592        0
593    }
594
595    fn last_modified(&self) -> u64 {
596        0
597    }
598
599    fn created_time(&self) -> u64 {
600        0
601    }
602
603    fn size(&self) -> u64 {
604        0
605    }
606
607    fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
608        Ok(())
609    }
610
611    fn unlink(&mut self) -> Result<()> {
612        Ok(())
613    }
614
615    fn get_special_fd(&self) -> Option<u32> {
616        Some(1)
617    }
618
619    fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
620        Poll::Ready(Ok(0))
621    }
622
623    fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
624        Poll::Ready(Ok(DEFAULT_BUF_SIZE_HINT))
625    }
626}
627
628impl AsyncRead for Stdout {
629    fn poll_read(
630        self: Pin<&mut Self>,
631        _cx: &mut Context<'_>,
632        _buf: &mut tokio::io::ReadBuf<'_>,
633    ) -> Poll<io::Result<()>> {
634        Poll::Ready(Err(io::Error::other("can not read from stdout")))
635    }
636}
637
638impl AsyncWrite for Stdout {
639    fn poll_write(
640        mut self: Pin<&mut Self>,
641        cx: &mut Context<'_>,
642        buf: &[u8],
643    ) -> Poll<io::Result<usize>> {
644        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
645        let inner = Pin::new(&mut self.inner);
646        inner.poll_write(cx, buf)
647    }
648
649    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
650        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
651        let inner = Pin::new(&mut self.inner);
652        inner.poll_flush(cx)
653    }
654
655    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
656        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
657        let inner = Pin::new(&mut self.inner);
658        inner.poll_shutdown(cx)
659    }
660
661    fn poll_write_vectored(
662        mut self: Pin<&mut Self>,
663        cx: &mut Context<'_>,
664        bufs: &[io::IoSlice<'_>],
665    ) -> Poll<io::Result<usize>> {
666        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
667        let inner = Pin::new(&mut self.inner);
668        inner.poll_write_vectored(cx, bufs)
669    }
670
671    fn is_write_vectored(&self) -> bool {
672        self.inner.is_write_vectored()
673    }
674}
675
676impl AsyncSeek for Stdout {
677    fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
678        Err(io::Error::other("can not seek stdout"))
679    }
680
681    fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
682        Poll::Ready(Err(io::Error::other("can not seek stdout")))
683    }
684}
685
686/// A wrapper type around Stderr that implements `VirtualFile`.
687#[derive(Debug)]
688pub struct Stderr {
689    handle: Handle,
690    inner: tokio::io::Stderr,
691}
692#[allow(dead_code)]
693fn default_stderr() -> tokio::io::Stderr {
694    tokio::io::stderr()
695}
696impl Default for Stderr {
697    fn default() -> Self {
698        Self {
699            handle: Handle::current(),
700            inner: tokio::io::stderr(),
701        }
702    }
703}
704
705impl AsyncRead for Stderr {
706    fn poll_read(
707        self: Pin<&mut Self>,
708        _cx: &mut Context<'_>,
709        _buf: &mut tokio::io::ReadBuf<'_>,
710    ) -> Poll<io::Result<()>> {
711        Poll::Ready(Err(io::Error::other("can not read from stderr")))
712    }
713}
714
715impl AsyncWrite for Stderr {
716    fn poll_write(
717        mut self: Pin<&mut Self>,
718        cx: &mut Context<'_>,
719        buf: &[u8],
720    ) -> Poll<io::Result<usize>> {
721        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
722        let inner = Pin::new(&mut self.inner);
723        inner.poll_write(cx, buf)
724    }
725
726    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
727        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
728        let inner = Pin::new(&mut self.inner);
729        inner.poll_flush(cx)
730    }
731
732    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
733        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
734        let inner = Pin::new(&mut self.inner);
735        inner.poll_shutdown(cx)
736    }
737
738    fn poll_write_vectored(
739        mut self: Pin<&mut Self>,
740        cx: &mut Context<'_>,
741        bufs: &[io::IoSlice<'_>],
742    ) -> Poll<io::Result<usize>> {
743        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
744        let inner = Pin::new(&mut self.inner);
745        inner.poll_write_vectored(cx, bufs)
746    }
747
748    fn is_write_vectored(&self) -> bool {
749        self.inner.is_write_vectored()
750    }
751}
752
753impl AsyncSeek for Stderr {
754    fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
755        Err(io::Error::other("can not seek stderr"))
756    }
757
758    fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
759        Poll::Ready(Err(io::Error::other("can not seek stderr")))
760    }
761}
762
763#[async_trait::async_trait]
764impl VirtualFile for Stderr {
765    fn last_accessed(&self) -> u64 {
766        0
767    }
768
769    fn last_modified(&self) -> u64 {
770        0
771    }
772
773    fn created_time(&self) -> u64 {
774        0
775    }
776
777    fn size(&self) -> u64 {
778        0
779    }
780
781    fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
782        Ok(())
783    }
784
785    fn unlink(&mut self) -> Result<()> {
786        Ok(())
787    }
788
789    fn get_special_fd(&self) -> Option<u32> {
790        Some(2)
791    }
792
793    fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
794        Poll::Ready(Ok(0))
795    }
796
797    fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
798        Poll::Ready(Ok(8192))
799    }
800}
801
802/// A wrapper type around Stdin that implements `VirtualFile`.
803#[derive(Debug)]
804pub struct Stdin {
805    read_buffer: Arc<std::sync::Mutex<Option<Bytes>>>,
806    handle: Handle,
807    inner: tokio::io::Stdin,
808}
809#[allow(dead_code)]
810fn default_stdin() -> tokio::io::Stdin {
811    tokio::io::stdin()
812}
813impl Default for Stdin {
814    fn default() -> Self {
815        Self {
816            handle: Handle::current(),
817            read_buffer: Arc::new(std::sync::Mutex::new(None)),
818            inner: tokio::io::stdin(),
819        }
820    }
821}
822
823impl AsyncRead for Stdin {
824    fn poll_read(
825        mut self: Pin<&mut Self>,
826        cx: &mut Context<'_>,
827        buf: &mut tokio::io::ReadBuf<'_>,
828    ) -> Poll<io::Result<()>> {
829        let max_size = buf.remaining();
830        {
831            let mut read_buffer = self.read_buffer.lock().unwrap();
832            if let Some(read_buffer) = read_buffer.as_mut() {
833                let buf_len = read_buffer.len();
834                if buf_len > 0 {
835                    let read = buf_len.min(max_size);
836                    buf.put_slice(&read_buffer[..read]);
837                    read_buffer.advance(read);
838                    return Poll::Ready(Ok(()));
839                }
840            }
841        }
842
843        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
844        let inner = Pin::new(&mut self.inner);
845        inner.poll_read(cx, buf)
846    }
847}
848
849impl AsyncWrite for Stdin {
850    fn poll_write(
851        self: Pin<&mut Self>,
852        _cx: &mut Context<'_>,
853        _buf: &[u8],
854    ) -> Poll<io::Result<usize>> {
855        Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
856    }
857
858    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
859        Poll::Ready(Err(io::Error::other("can not flush stdin")))
860    }
861
862    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
863        Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
864    }
865
866    fn poll_write_vectored(
867        self: Pin<&mut Self>,
868        _cx: &mut Context<'_>,
869        _bufs: &[io::IoSlice<'_>],
870    ) -> Poll<io::Result<usize>> {
871        Poll::Ready(Err(io::Error::other("can not wrote to stdin")))
872    }
873}
874
875impl AsyncSeek for Stdin {
876    fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
877        Err(io::Error::other("can not seek stdin"))
878    }
879
880    fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
881        Poll::Ready(Err(io::Error::other("can not seek stdin")))
882    }
883}
884
885#[async_trait::async_trait]
886impl VirtualFile for Stdin {
887    fn last_accessed(&self) -> u64 {
888        0
889    }
890    fn last_modified(&self) -> u64 {
891        0
892    }
893    fn created_time(&self) -> u64 {
894        0
895    }
896    fn size(&self) -> u64 {
897        0
898    }
899    fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
900        Ok(())
901    }
902    fn unlink(&mut self) -> Result<()> {
903        Ok(())
904    }
905    fn get_special_fd(&self) -> Option<u32> {
906        Some(0)
907    }
908    fn poll_read_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
909        {
910            let read_buffer = self.read_buffer.lock().unwrap();
911            if let Some(read_buffer) = read_buffer.as_ref() {
912                let buf_len = read_buffer.len();
913                if buf_len > 0 {
914                    return Poll::Ready(Ok(buf_len));
915                }
916            }
917        }
918
919        let _guard = Handle::try_current().map_err(|_| self.handle.enter());
920        let inner = Pin::new(&mut self.inner);
921
922        let mut buf = [0u8; 8192];
923        let mut read_buf = ReadBuf::new(&mut buf[..]);
924        match inner.poll_read(cx, &mut read_buf) {
925            Poll::Pending => Poll::Pending,
926            Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
927            Poll::Ready(Ok(())) => {
928                let buf = read_buf.filled();
929                let buf_len = buf.len();
930
931                let mut read_buffer = self.read_buffer.lock().unwrap();
932                read_buffer.replace(Bytes::from(buf.to_vec()));
933                Poll::Ready(Ok(buf_len))
934            }
935        }
936    }
937    fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
938        Poll::Ready(Ok(0))
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use tempfile::TempDir;
945    use tokio::runtime::Handle;
946
947    use super::FileSystem;
948    use crate::FileSystem as FileSystemTrait;
949    use crate::FsError;
950    use std::path::Path;
951
952    #[tokio::test]
953    async fn test_new_filesystem() {
954        let temp = TempDir::new().unwrap();
955        std::fs::write(temp.path().join("foo2.txt"), b"").unwrap();
956
957        let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
958        assert!(
959            fs.read_dir(Path::new("/")).is_ok(),
960            "NativeFS can read root"
961        );
962        assert!(
963            fs.new_open_options()
964                .read(true)
965                .open(Path::new("/foo2.txt"))
966                .is_ok(),
967            "created foo2.txt"
968        );
969    }
970
971    #[tokio::test]
972    async fn test_create_dir() {
973        let temp: TempDir = TempDir::new().unwrap();
974        let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
975
976        assert_eq!(
977            fs.create_dir(Path::new("../")),
978            Err(FsError::AlreadyExists),
979            "creating a directory out of bounds",
980        );
981
982        assert_eq!(
983            fs.create_dir(Path::new("/foo")),
984            Ok(()),
985            "creating a directory",
986        );
987
988        assert!(
989            temp.path().join("foo").exists(),
990            "foo dir exists in host_fs"
991        );
992
993        let cur_dir = read_dir_names(&fs, "/");
994
995        if !cur_dir.contains(&"foo".to_string()) {
996            panic!("cur_dir does not contain foo: {cur_dir:#?}");
997        }
998
999        assert!(
1000            cur_dir.contains(&"foo".to_string()),
1001            "the root is updated and well-defined"
1002        );
1003
1004        assert_eq!(
1005            fs.create_dir(Path::new("foo/bar")),
1006            Ok(()),
1007            "creating a sub-directory",
1008        );
1009
1010        assert!(
1011            temp.path().join("foo").join("bar").exists(),
1012            "foo dir exists in host_fs"
1013        );
1014
1015        let foo_dir = read_dir_names(&fs, Path::new("/foo"));
1016
1017        assert!(
1018            foo_dir.contains(&"bar".to_string()),
1019            "the foo directory is updated and well-defined"
1020        );
1021
1022        let bar_dir = read_dir_names(&fs, Path::new("/foo/bar"));
1023
1024        assert!(
1025            bar_dir.is_empty(),
1026            "the foo directory is updated and well-defined"
1027        );
1028    }
1029
1030    #[tokio::test]
1031    async fn test_remove_dir() {
1032        let temp: TempDir = TempDir::new().unwrap();
1033        let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1034
1035        assert_eq!(
1036            fs.remove_dir(Path::new("/foo")),
1037            Err(FsError::EntryNotFound),
1038            "cannot remove a directory that doesn't exist",
1039        );
1040
1041        assert_eq!(
1042            fs.create_dir(Path::new("foo")),
1043            Ok(()),
1044            "creating a directory",
1045        );
1046
1047        assert_eq!(
1048            fs.create_dir(Path::new("foo/bar")),
1049            Ok(()),
1050            "creating a sub-directory",
1051        );
1052
1053        assert!(temp.path().join("foo/bar").exists(), "./foo/bar exists");
1054
1055        assert_eq!(
1056            fs.remove_dir(Path::new("foo")),
1057            Err(FsError::DirectoryNotEmpty),
1058            "removing a directory that has children",
1059        );
1060
1061        assert_eq!(
1062            fs.remove_dir(Path::new("foo/bar")),
1063            Ok(()),
1064            "removing a sub-directory",
1065        );
1066
1067        assert_eq!(
1068            fs.remove_dir(Path::new("foo")),
1069            Ok(()),
1070            "removing a directory",
1071        );
1072
1073        let cur_dir = read_dir_names(&fs, "/");
1074
1075        assert!(
1076            !cur_dir.contains(&"foo".to_string()),
1077            "the foo directory still exists"
1078        );
1079    }
1080
1081    fn read_dir_names(fs: &FileSystem, path: impl AsRef<Path>) -> Vec<String> {
1082        fs.read_dir(path.as_ref())
1083            .unwrap()
1084            .filter_map(|entry| Some(entry.ok()?.file_name().to_str()?.to_string()))
1085            .collect::<Vec<_>>()
1086    }
1087
1088    #[tokio::test]
1089    async fn test_rename() {
1090        let temp: TempDir = TempDir::new().unwrap();
1091        let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1092        std::fs::create_dir_all(temp.path().join("foo").join("qux")).unwrap();
1093        let foo = Path::new("foo");
1094        let bar = Path::new("bar");
1095        let foo_realpath = temp.path().join(foo);
1096        let bar_realpath = temp.path().join(bar);
1097
1098        assert_eq!(
1099            fs.rename(Path::new("/"), Path::new("/bar")).await,
1100            Err(FsError::BaseNotDirectory),
1101            "renaming a directory that has no parent",
1102        );
1103        assert_eq!(
1104            fs.rename(Path::new("/foo"), Path::new("/")).await,
1105            Err(FsError::BaseNotDirectory),
1106            "renaming to a directory that has no parent",
1107        );
1108
1109        assert_eq!(
1110            fs.rename(foo, &foo.join("bar").join("baz"),).await,
1111            Err(FsError::EntryNotFound),
1112            "renaming to a directory that has parent that doesn't exist",
1113        );
1114
1115        // On Windows, rename "to" must not be an existing directory
1116        #[cfg(not(target_os = "windows"))]
1117        assert_eq!(fs.create_dir(bar), Ok(()));
1118
1119        assert_eq!(
1120            fs.rename(foo, bar).await,
1121            Ok(()),
1122            "renaming to a directory that has parent that exists",
1123        );
1124
1125        assert!(
1126            fs.new_open_options()
1127                .write(true)
1128                .create_new(true)
1129                .open(bar.join("hello1.txt"))
1130                .is_ok(),
1131            "creating a new file (`hello1.txt`)",
1132        );
1133        assert!(
1134            fs.new_open_options()
1135                .write(true)
1136                .create_new(true)
1137                .open(bar.join("hello2.txt"))
1138                .is_ok(),
1139            "creating a new file (`hello2.txt`)",
1140        );
1141
1142        let cur_dir = read_dir_names(&fs, Path::new("/"));
1143
1144        assert!(
1145            !cur_dir.contains(&"foo".to_string()),
1146            "the foo directory still exists"
1147        );
1148
1149        assert!(
1150            cur_dir.contains(&"bar".to_string()),
1151            "the bar directory still exists"
1152        );
1153
1154        let bar_dir = read_dir_names(&fs, bar);
1155
1156        if !bar_dir.contains(&"qux".to_string()) {
1157            println!("qux does not exist: {bar_dir:?}")
1158        }
1159
1160        let qux_dir = read_dir_names(&fs, bar.join("qux"));
1161
1162        assert!(qux_dir.is_empty(), "the qux directory is empty");
1163
1164        assert!(
1165            bar_realpath.join("hello1.txt").exists(),
1166            "the /bar/hello1.txt file exists"
1167        );
1168
1169        assert!(
1170            bar_realpath.join("hello2.txt").exists(),
1171            "the /bar/hello2.txt file exists"
1172        );
1173
1174        assert_eq!(fs.create_dir(foo), Ok(()), "create ./foo again");
1175
1176        assert_eq!(
1177            fs.rename(&bar.join("hello2.txt"), &foo.join("world2.txt"))
1178                .await,
1179            Ok(()),
1180            "renaming (and moving) a file",
1181        );
1182
1183        assert_eq!(
1184            fs.rename(foo, &bar.join("baz")).await,
1185            Ok(()),
1186            "renaming a directory",
1187        );
1188
1189        assert_eq!(
1190            fs.rename(&bar.join("hello1.txt"), &bar.join("world1.txt"))
1191                .await,
1192            Ok(()),
1193            "renaming a file (in the same directory)",
1194        );
1195
1196        assert!(bar_realpath.exists(), "./bar exists");
1197        assert!(bar_realpath.join("baz").exists(), "./bar/baz exists");
1198        assert!(!foo_realpath.exists(), "foo does not exist anymore");
1199        assert!(
1200            bar_realpath.join("baz/world2.txt").exists(),
1201            "/bar/baz/world2.txt exists"
1202        );
1203        assert!(
1204            bar_realpath.join("world1.txt").exists(),
1205            "/bar/world1.txt (ex hello1.txt) exists"
1206        );
1207        assert!(
1208            !bar_realpath.join("hello1.txt").exists(),
1209            "hello1.txt was moved"
1210        );
1211        assert!(
1212            !bar_realpath.join("hello2.txt").exists(),
1213            "hello2.txt was moved"
1214        );
1215        assert!(
1216            bar_realpath.join("baz/world2.txt").exists(),
1217            "world2.txt was moved to the correct place"
1218        );
1219    }
1220
1221    #[tokio::test]
1222    async fn test_metadata() {
1223        use std::thread::sleep;
1224        use std::time::Duration;
1225
1226        let temp = TempDir::new().unwrap();
1227
1228        let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1229
1230        let root_metadata = fs.metadata(Path::new("/")).unwrap();
1231
1232        assert!(root_metadata.ft.dir);
1233        // it seems created is not available on musl, at least on CI testing.
1234        #[cfg(not(target_env = "musl"))]
1235        assert_eq!(root_metadata.accessed, root_metadata.created);
1236        #[cfg(not(target_env = "musl"))]
1237        assert_eq!(root_metadata.modified, root_metadata.created);
1238        assert!(root_metadata.modified > 0);
1239
1240        let foo = Path::new("foo");
1241
1242        assert_eq!(fs.create_dir(foo), Ok(()));
1243
1244        let foo_metadata = fs.metadata(foo);
1245        assert!(foo_metadata.is_ok());
1246        let foo_metadata = foo_metadata.unwrap();
1247
1248        assert!(foo_metadata.ft.dir);
1249        #[cfg(not(target_env = "musl"))]
1250        assert_eq!(foo_metadata.accessed, foo_metadata.created);
1251        #[cfg(not(target_env = "musl"))]
1252        assert_eq!(foo_metadata.modified, foo_metadata.created);
1253        assert!(foo_metadata.modified > 0);
1254
1255        sleep(Duration::from_secs(3));
1256
1257        let bar = Path::new("bar");
1258
1259        assert_eq!(fs.rename(foo, bar).await, Ok(()));
1260
1261        let bar_metadata = fs.metadata(bar).unwrap();
1262        assert!(bar_metadata.ft.dir);
1263        assert!(bar_metadata.accessed >= foo_metadata.accessed);
1264        assert_eq!(bar_metadata.created, foo_metadata.created);
1265        assert!(bar_metadata.modified > foo_metadata.modified);
1266
1267        let root_metadata = fs.metadata(bar).unwrap();
1268        assert!(
1269            root_metadata.modified > foo_metadata.modified,
1270            "the parent modified time was updated"
1271        );
1272    }
1273
1274    #[tokio::test]
1275    async fn test_rejects_host_absolute_paths_inside_root() {
1276        let temp = TempDir::new().unwrap();
1277        // Some platforms (e.g. mac) symlink /tmp to /private/tmp, so we need to canonicalize
1278        // the path to get the real one, making sure the guest and host paths line up.
1279        let temp_canon = super::canonicalize(temp.path()).expect("canonicalize temp dir");
1280
1281        let file_path = temp_canon.join("foo.txt");
1282        std::fs::write(&file_path, b"hello").unwrap();
1283
1284        let fs = FileSystem::new(Handle::current(), &temp_canon).expect("get filesystem");
1285
1286        assert_eq!(fs.metadata(&file_path), Err(FsError::InvalidInput));
1287        assert!(matches!(
1288            fs.new_open_options().read(true).open(&file_path),
1289            Err(FsError::InvalidInput)
1290        ));
1291    }
1292
1293    #[tokio::test]
1294    async fn test_remove_file() {
1295        let temp = TempDir::new().unwrap();
1296        let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1297
1298        assert!(
1299            fs.new_open_options()
1300                .write(true)
1301                .create_new(true)
1302                .open(Path::new("foo.txt"))
1303                .is_ok(),
1304            "creating a new file",
1305        );
1306
1307        assert!(read_dir_names(&fs, Path::new("/")).contains(&"foo.txt".to_string()));
1308
1309        assert!(temp.path().join("foo.txt").is_file());
1310
1311        assert_eq!(
1312            fs.remove_file(Path::new("foo.txt")),
1313            Ok(()),
1314            "removing a file that exists",
1315        );
1316
1317        assert!(!temp.path().join("foo.txt").exists());
1318
1319        assert_eq!(
1320            fs.remove_file(Path::new("foo.txt")),
1321            Err(FsError::EntryNotFound),
1322            "removing a file that doesn't exists",
1323        );
1324    }
1325
1326    #[tokio::test]
1327    async fn test_readdir() {
1328        let temp = TempDir::new().unwrap();
1329        let fs = FileSystem::new(Handle::current(), temp.path()).expect("get filesystem");
1330
1331        assert_eq!(fs.create_dir(Path::new("foo")), Ok(()), "creating `foo`");
1332        assert_eq!(
1333            fs.create_dir(Path::new("foo/sub")),
1334            Ok(()),
1335            "creating `sub`"
1336        );
1337        assert_eq!(fs.create_dir(Path::new("bar")), Ok(()), "creating `bar`");
1338        assert_eq!(fs.create_dir(Path::new("baz")), Ok(()), "creating `bar`");
1339        assert!(
1340            fs.new_open_options()
1341                .write(true)
1342                .create_new(true)
1343                .open(Path::new("a.txt"))
1344                .is_ok(),
1345            "creating `a.txt`",
1346        );
1347        assert!(
1348            fs.new_open_options()
1349                .write(true)
1350                .create_new(true)
1351                .open(Path::new("b.txt"))
1352                .is_ok(),
1353            "creating `b.txt`",
1354        );
1355
1356        let readdir = fs.read_dir(Path::new("/"));
1357
1358        assert!(
1359            readdir.is_ok(),
1360            "reading the directory `{}`",
1361            Path::new("/").display()
1362        );
1363
1364        let mut readdir = readdir.unwrap();
1365
1366        let next = readdir.next().unwrap().unwrap();
1367        assert!(next.path.ends_with("a.txt"), "checking entry #1");
1368        assert!(next.metadata().unwrap().is_file(), "checking entry #1");
1369
1370        let next = readdir.next().unwrap().unwrap();
1371        assert!(next.path.ends_with("b.txt"), "checking entry #2");
1372        assert!(next.metadata().unwrap().is_file(), "checking entry #2");
1373
1374        let next = readdir.next().unwrap().unwrap();
1375        assert!(next.path.ends_with("bar"), "checking entry #3");
1376        assert!(next.metadata().unwrap().is_dir(), "checking entry #3");
1377
1378        let next = readdir.next().unwrap().unwrap();
1379        assert!(next.path.ends_with("baz"), "checking entry #4");
1380        assert!(next.metadata().unwrap().is_dir(), "checking entry #4");
1381
1382        let next = readdir.next().unwrap().unwrap();
1383        assert!(next.path.ends_with("foo"), "checking entry #5");
1384        assert!(next.metadata().unwrap().is_dir(), "checking entry #5");
1385
1386        if let Some(s) = readdir.next() {
1387            panic!("next: {s:?}");
1388        }
1389    }
1390}