Skip to main content

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