1use std::{
2 fs::{File, OpenOptions, ReadDir, read_dir},
3 future::Future,
4 io::{self, Read, SeekFrom},
5 path::{Path, PathBuf},
6 pin::Pin,
7 sync::{Arc, Mutex, mpsc},
8 task::{Context, Poll},
9};
10
11use tokio::runtime::Handle;
12use virtual_fs::{
13 AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt, FileSystem, Pipe, ReadBuf,
14 RootFileSystemBuilder, host_fs, mem_fs, passthru_fs, tmp_fs, union_fs,
15};
16use wasmer::{FunctionEnv, Imports, Module, Store};
17use wasmer_types::ModuleHash;
18use wasmer_wasix::runtime::task_manager::{InlineWaker, tokio::TokioTaskManager};
19use wasmer_wasix::types::wasi::{Filesize, Timestamp};
20use wasmer_wasix::{
21 FsError, PluggableRuntime, VirtualFile, WasiEnv, WasiEnvBuilder, WasiVersion,
22 generate_import_object_from_env, get_wasi_version,
23};
24use wast::parser::{self, Parse, ParseBuffer, Parser};
25
26#[derive(Debug)]
28pub enum WasiFileSystemKind {
29 Host,
31
32 InMemory,
34
35 Tmp,
37
38 PassthruMemory,
40
41 UnionHostMemory,
43
44 RootFileSystemBuilder,
46}
47
48#[derive(Debug, Clone, Hash)]
50pub struct WasiTest<'a> {
51 wasm_path: &'a str,
52 args: Vec<&'a str>,
53 envs: Vec<(&'a str, &'a str)>,
54 dirs: Vec<&'a str>,
55 mapped_dirs: Vec<(&'a str, &'a str)>,
56 temp_dirs: Vec<&'a str>,
57 assert_return: Option<AssertReturn>,
58 stdin: Option<Stdin<'a>>,
59 assert_stdout: Option<AssertStdout<'a>>,
60 assert_stderr: Option<AssertStderr<'a>>,
61}
62
63const BASE_TEST_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../wasi-wast/wasi/");
65
66fn get_stdio_output(rx: &mpsc::Receiver<Vec<u8>>) -> anyhow::Result<String> {
67 let mut stdio = Vec::new();
68 while let Ok(mut buf) = rx.try_recv() {
69 stdio.append(&mut buf);
70 }
71 let stdout_str = std::str::from_utf8(&stdio[..])?;
72 #[cfg(target_os = "windows")]
73 return Ok(stdout_str.replace("\r\n", "\n"));
75
76 #[cfg(not(target_os = "windows"))]
77 return Ok(stdout_str.to_string());
78}
79
80#[allow(dead_code)]
81impl<'a> WasiTest<'a> {
82 pub fn lex_string(wast: &'a str) -> parser::Result<ParseBuffer<'a>> {
84 ParseBuffer::new(wast)
85 }
86
87 pub fn parse_tokens(tokens: &'a ParseBuffer<'a>) -> parser::Result<Self> {
89 parser::parse(tokens)
90 }
91
92 pub fn run(
94 &self,
95 mut store: &mut Store,
96 base_path: &str,
97 filesystem_kind: WasiFileSystemKind,
98 ) -> anyhow::Result<bool> {
99 use anyhow::Context;
100
101 #[cfg(not(target_arch = "wasm32"))]
102 let runtime = tokio::runtime::Builder::new_multi_thread()
103 .enable_all()
104 .build()?;
105 #[cfg(not(target_arch = "wasm32"))]
106 let handle = runtime.handle().clone();
107 #[cfg(not(target_arch = "wasm32"))]
108 let _guard = handle.enter();
109 #[cfg(not(target_arch = "wasm32"))]
110 let mut rt = PluggableRuntime::new(Arc::new(TokioTaskManager::new(runtime)));
111 #[cfg(target_arch = "wasm32")]
112 let mut rt = PluggableRuntime::new(Arc::new(TokioTaskManager::default()));
113 rt.set_engine(store.engine().clone());
114
115 let mut pb = PathBuf::from(base_path);
116 pb.push(self.wasm_path);
117 let wasm_bytes = {
118 let mut wasm_module = File::open(pb)?;
119 let mut out = vec![];
120 wasm_module.read_to_end(&mut out)?;
121 out
122 };
123 let module_hash = ModuleHash::xxhash(&wasm_bytes);
124
125 let module = Module::new(store, wasm_bytes)?;
126 let (builder, _tempdirs, mut stdin_tx, stdout_rx, stderr_rx) =
127 { InlineWaker::block_on(async { self.create_wasi_env(filesystem_kind).await }) }?;
128
129 let (instance, _wasi_env) =
130 builder
131 .runtime(Arc::new(rt))
132 .instantiate_ext(module, module_hash, store)?;
133
134 let start = instance.exports.get_function("_start")?;
135
136 if let Some(stdin) = &self.stdin {
137 let data = stdin.stream.to_string();
140 InlineWaker::block_on(async move {
141 stdin_tx.write_all(data.as_bytes()).await?;
142 stdin_tx.shutdown().await?;
143
144 Ok::<_, anyhow::Error>(())
145 })?;
146 } else {
147 std::mem::drop(stdin_tx);
148 }
149
150 match start.call(&mut store, &[]) {
152 Ok(_) => {}
153 Err(e) => {
154 let stdout_str = get_stdio_output(&stdout_rx)?;
155 let stderr_str = get_stdio_output(&stderr_rx)?;
156 Err(e).with_context(|| {
157 format!(
158 "failed to run WASI `_start` function: failed with stdout: \"{stdout_str}\"\nstderr: \"{stderr_str}\"",
159 )
160 })?;
161 }
162 }
163
164 if let Some(expected_stdout) = &self.assert_stdout {
165 let stdout_str = get_stdio_output(&stdout_rx)?;
166 assert_eq!(stdout_str, expected_stdout.expected);
168 }
169
170 if let Some(expected_stderr) = &self.assert_stderr {
171 let stderr_str = get_stdio_output(&stderr_rx)?;
172 assert_eq!(stderr_str, expected_stderr.expected);
173 }
174
175 Ok(true)
176 }
177
178 #[allow(clippy::type_complexity)]
180 async fn create_wasi_env(
181 &self,
182 filesystem_kind: WasiFileSystemKind,
183 ) -> anyhow::Result<(
184 WasiEnvBuilder,
185 Vec<tempfile::TempDir>,
186 Pipe,
187 mpsc::Receiver<Vec<u8>>,
188 mpsc::Receiver<Vec<u8>>,
189 )> {
190 let mut builder = WasiEnv::builder(self.wasm_path);
191
192 let (stdin_tx, stdin_rx) = Pipe::channel();
193 builder.set_stdin(Box::new(stdin_rx));
194
195 for (name, value) in &self.envs {
196 builder.add_env(name, value);
197 }
198
199 let mut host_temp_dirs_to_not_drop = vec![];
200
201 match filesystem_kind {
202 WasiFileSystemKind::Host => {
203 let fs = host_fs::FileSystem::new(Handle::current(), PathBuf::from(BASE_TEST_DIR))
204 .unwrap();
205
206 for (alias, real_dir) in &self.mapped_dirs {
207 let mut dir = PathBuf::from(BASE_TEST_DIR);
208 dir.push(real_dir);
209 builder.add_map_dir(alias, dir)?;
210 }
211
212 for dir in &self.dirs {
214 let mut new_dir = PathBuf::from(BASE_TEST_DIR);
215 new_dir.push(dir);
216 builder.add_map_dir(dir, new_dir)?;
217 }
218
219 for alias in &self.temp_dirs {
220 let temp_dir = tempfile::tempdir_in(PathBuf::from(BASE_TEST_DIR))?;
221 builder.add_map_dir(alias, temp_dir.path())?;
222 host_temp_dirs_to_not_drop.push(temp_dir);
223 }
224
225 builder.set_fs(Box::new(fs));
226 }
227
228 other => {
229 let fs: Box<dyn FileSystem + Send + Sync> = match other {
230 WasiFileSystemKind::InMemory => Box::<mem_fs::FileSystem>::default(),
231 WasiFileSystemKind::Tmp => Box::<tmp_fs::TmpFileSystem>::default(),
232 WasiFileSystemKind::PassthruMemory => {
233 let fs = Box::<mem_fs::FileSystem>::default();
234 Box::new(passthru_fs::PassthruFileSystem::new(fs))
235 }
236 WasiFileSystemKind::RootFileSystemBuilder => {
237 Box::new(RootFileSystemBuilder::new().build())
238 }
239 WasiFileSystemKind::UnionHostMemory => {
240 let a = mem_fs::FileSystem::default();
241 let b = mem_fs::FileSystem::default();
242 let c = mem_fs::FileSystem::default();
243 let d = mem_fs::FileSystem::default();
244 let e = mem_fs::FileSystem::default();
245 let f = mem_fs::FileSystem::default();
246
247 let union = union_fs::UnionFileSystem::new();
248
249 union.mount(
250 "mem_fs".to_string(),
251 PathBuf::from("/test_fs").as_ref(),
252 Box::new(a),
253 )?;
254 union.mount(
255 "mem_fs_2".to_string(),
256 PathBuf::from("/snapshot1").as_ref(),
257 Box::new(b),
258 )?;
259 union.mount(
260 "mem_fs_3".to_string(),
261 PathBuf::from("/tests").as_ref(),
262 Box::new(c),
263 )?;
264 union.mount(
265 "mem_fs_4".to_string(),
266 PathBuf::from("/nightly_2022_10_18").as_ref(),
267 Box::new(d),
268 )?;
269 union.mount(
270 "mem_fs_5".to_string(),
271 PathBuf::from("/unstable").as_ref(),
272 Box::new(e),
273 )?;
274 union.mount(
275 "mem_fs_6".to_string(),
276 PathBuf::from("/.tmp_wasmer_wast_0").as_ref(),
277 Box::new(f),
278 )?;
279
280 Box::new(union)
281 }
282 _ => {
283 panic!("unexpected filesystem type {other:?}");
284 }
285 };
286
287 let mut temp_dir_index: usize = 0;
288
289 let root = PathBuf::from("/");
290
291 map_host_fs_to_mem_fs(&*fs, read_dir(BASE_TEST_DIR)?, &root).await?;
292
293 for (alias, real_dir) in &self.mapped_dirs {
294 let mut path = root.clone();
295 path.push(real_dir);
296 builder.add_map_dir(alias, path)?;
297 }
298
299 for dir in &self.dirs {
300 let mut new_dir = PathBuf::from("/");
301 new_dir.push(dir);
302
303 builder.add_map_dir(dir, new_dir)?;
304 }
305
306 for alias in &self.temp_dirs {
307 let temp_dir_name =
308 PathBuf::from(format!("/.tmp_wasmer_wast_{temp_dir_index}"));
309 fs.create_dir(temp_dir_name.as_path())?;
310 builder.add_map_dir(alias, temp_dir_name)?;
311 temp_dir_index += 1;
312 }
313
314 builder.set_fs(fs);
315 }
316 }
317
318 let (stdout, stdout_rx) = OutputCapturerer::new();
319 let (stderr, stderr_rx) = OutputCapturerer::new();
320 let builder = builder
321 .args(&self.args)
322 .stdout(Box::new(stdout))
325 .stderr(Box::new(stderr));
326
327 Ok((
328 builder,
329 host_temp_dirs_to_not_drop,
330 stdin_tx,
331 stdout_rx,
332 stderr_rx,
333 ))
334 }
335
336 fn get_version(&self, module: &Module) -> anyhow::Result<WasiVersion> {
338 use anyhow::Context;
339 let version = get_wasi_version(module, true)
340 .with_context(|| "failed to detect a version of WASI from the module")?;
341 Ok(version)
342 }
343
344 fn get_imports(
347 &self,
348 store: &mut Store,
349 ctx: &FunctionEnv<WasiEnv>,
350 module: &Module,
351 ) -> anyhow::Result<Imports> {
352 let version = self.get_version(module)?;
353 Ok(generate_import_object_from_env(store, ctx, version))
354 }
355}
356
357mod wasi_kw {
358 wast::custom_keyword!(wasi_test);
359 wast::custom_keyword!(envs);
360 wast::custom_keyword!(args);
361 wast::custom_keyword!(preopens);
362 wast::custom_keyword!(map_dirs);
363 wast::custom_keyword!(temp_dirs);
364 wast::custom_keyword!(assert_return);
365 wast::custom_keyword!(stdin);
366 wast::custom_keyword!(assert_stdout);
367 wast::custom_keyword!(assert_stderr);
368 wast::custom_keyword!(fake_i64_const = "i64.const");
369}
370
371impl<'a> Parse<'a> for WasiTest<'a> {
372 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
373 parser.parens(|parser| {
374 parser.parse::<wasi_kw::wasi_test>()?;
375 let wasm_path = parser.parse::<&'a str>()?;
377
378 let envs = if parser.peek2::<wasi_kw::envs>()? {
380 parser.parens(|p| p.parse::<Envs>())?.envs
381 } else {
382 vec![]
383 };
384
385 let args = if parser.peek2::<wasi_kw::args>()? {
386 parser.parens(|p| p.parse::<Args>())?.args
387 } else {
388 vec![]
389 };
390
391 let dirs = if parser.peek2::<wasi_kw::preopens>()? {
392 parser.parens(|p| p.parse::<Preopens>())?.preopens
393 } else {
394 vec![]
395 };
396
397 let mapped_dirs = if parser.peek2::<wasi_kw::map_dirs>()? {
398 parser.parens(|p| p.parse::<MapDirs>())?.map_dirs
399 } else {
400 vec![]
401 };
402
403 let temp_dirs = if parser.peek2::<wasi_kw::temp_dirs>()? {
404 parser.parens(|p| p.parse::<TempDirs>())?.temp_dirs
405 } else {
406 vec![]
407 };
408
409 let assert_return = if parser.peek2::<wasi_kw::assert_return>()? {
410 Some(parser.parens(|p| p.parse::<AssertReturn>())?)
411 } else {
412 None
413 };
414
415 let stdin = if parser.peek2::<wasi_kw::stdin>()? {
416 Some(parser.parens(|p| p.parse::<Stdin>())?)
417 } else {
418 None
419 };
420
421 let assert_stdout = if parser.peek2::<wasi_kw::assert_stdout>()? {
422 Some(parser.parens(|p| p.parse::<AssertStdout>())?)
423 } else {
424 None
425 };
426
427 let assert_stderr = if parser.peek2::<wasi_kw::assert_stderr>()? {
428 Some(parser.parens(|p| p.parse::<AssertStderr>())?)
429 } else {
430 None
431 };
432
433 Ok(Self {
434 wasm_path,
435 args,
436 envs,
437 dirs,
438 mapped_dirs,
439 temp_dirs,
440 assert_return,
441 stdin,
442 assert_stdout,
443 assert_stderr,
444 })
445 })
446 }
447}
448
449#[derive(Debug, Clone, Hash)]
450struct Envs<'a> {
451 envs: Vec<(&'a str, &'a str)>,
452}
453
454impl<'a> Parse<'a> for Envs<'a> {
455 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
456 let mut envs = vec![];
457 parser.parse::<wasi_kw::envs>()?;
458
459 while parser.peek::<&'a str>()? {
460 let res = parser.parse::<&'a str>()?;
461 let mut strs = res.split('=');
462 let first = strs.next().unwrap();
463 let second = strs.next().unwrap();
464 envs.push((first, second));
466 }
467 Ok(Self { envs })
468 }
469}
470
471#[derive(Debug, Clone, Hash)]
472struct Args<'a> {
473 args: Vec<&'a str>,
474}
475
476impl<'a> Parse<'a> for Args<'a> {
477 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
478 let mut args = vec![];
479 parser.parse::<wasi_kw::args>()?;
480
481 while parser.peek::<&'a str>()? {
482 let res = parser.parse::<&'a str>()?;
483 args.push(res);
484 }
485 Ok(Self { args })
486 }
487}
488
489#[derive(Debug, Clone, Hash)]
490struct Preopens<'a> {
491 preopens: Vec<&'a str>,
492}
493
494impl<'a> Parse<'a> for Preopens<'a> {
495 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
496 let mut preopens = vec![];
497 parser.parse::<wasi_kw::preopens>()?;
498
499 while parser.peek::<&'a str>()? {
500 let res = parser.parse::<&'a str>()?;
501 preopens.push(res);
502 }
503 Ok(Self { preopens })
504 }
505}
506
507#[derive(Debug, Clone, Hash)]
508struct MapDirs<'a> {
509 map_dirs: Vec<(&'a str, &'a str)>,
510}
511
512impl<'a> Parse<'a> for MapDirs<'a> {
513 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
514 let mut map_dirs = vec![];
515 parser.parse::<wasi_kw::map_dirs>()?;
516
517 while parser.peek::<&'a str>()? {
518 let res = parser.parse::<&'a str>()?;
519 let mut iter = res.split(':');
520 let dir = iter.next().unwrap();
521 let alias = iter.next().unwrap();
522 map_dirs.push((dir, alias));
523 }
524 Ok(Self { map_dirs })
525 }
526}
527
528#[derive(Debug, Clone, Hash)]
529struct TempDirs<'a> {
530 temp_dirs: Vec<&'a str>,
531}
532
533impl<'a> Parse<'a> for TempDirs<'a> {
534 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
535 let mut temp_dirs = vec![];
536 parser.parse::<wasi_kw::temp_dirs>()?;
537
538 while parser.peek::<&'a str>()? {
539 let alias = parser.parse::<&'a str>()?;
540 temp_dirs.push(alias);
541 }
542 Ok(Self { temp_dirs })
543 }
544}
545
546#[derive(Debug, Clone, PartialEq, Eq, Hash)]
547struct AssertReturn {
548 return_value: i64,
549}
550
551impl<'a> Parse<'a> for AssertReturn {
552 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
553 parser.parse::<wasi_kw::assert_return>()?;
554 let return_value = parser.parens(|p| {
555 p.parse::<wasi_kw::fake_i64_const>()?;
556 p.parse::<i64>()
557 })?;
558 Ok(Self { return_value })
559 }
560}
561
562#[derive(Debug, Clone, PartialEq, Eq, Hash)]
563struct Stdin<'a> {
564 stream: &'a str,
565}
566
567impl<'a> Parse<'a> for Stdin<'a> {
568 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
569 parser.parse::<wasi_kw::stdin>()?;
570 Ok(Self {
571 stream: parser.parse()?,
572 })
573 }
574}
575
576#[derive(Debug, Clone, PartialEq, Eq, Hash)]
577struct AssertStdout<'a> {
578 expected: &'a str,
579}
580
581impl<'a> Parse<'a> for AssertStdout<'a> {
582 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
583 parser.parse::<wasi_kw::assert_stdout>()?;
584 Ok(Self {
585 expected: parser.parse()?,
586 })
587 }
588}
589
590#[derive(Debug, Clone, PartialEq, Eq, Hash)]
591struct AssertStderr<'a> {
592 expected: &'a str,
593}
594
595impl<'a> Parse<'a> for AssertStderr<'a> {
596 fn parse(parser: Parser<'a>) -> parser::Result<Self> {
597 parser.parse::<wasi_kw::assert_stderr>()?;
598 Ok(Self {
599 expected: parser.parse()?,
600 })
601 }
602}
603
604#[cfg(test)]
605mod test {
606 use super::*;
607
608 #[tokio::test]
609 async fn test_parse() {
610 let pb = wast::parser::ParseBuffer::new(
611 r#"(wasi_test "my_wasm.wasm"
612 (envs "HELLO=WORLD" "RUST_BACKTRACE=1")
613 (args "hello" "world" "--help")
614 (preopens "." "src/io")
615 (assert_return (i64.const 0))
616 (stdin "This is another \"string\" inside a string!")
617 (assert_stdout "This is a \"string\" inside a string!")
618 (assert_stderr "")
619)"#,
620 )
621 .unwrap();
622 let result = wast::parser::parse::<WasiTest>(&pb).unwrap();
623
624 assert_eq!(result.args, vec!["hello", "world", "--help"]);
625 assert_eq!(
626 result.envs,
627 vec![("HELLO", "WORLD"), ("RUST_BACKTRACE", "1")]
628 );
629 assert_eq!(result.dirs, vec![".", "src/io"]);
630 assert_eq!(result.assert_return.unwrap().return_value, 0);
631 assert_eq!(
632 result.assert_stdout.unwrap().expected,
633 "This is a \"string\" inside a string!"
634 );
635 assert_eq!(
636 result.stdin.unwrap().stream,
637 "This is another \"string\" inside a string!"
638 );
639 assert_eq!(result.assert_stderr.unwrap().expected, "");
640 }
641}
642
643#[derive(Debug, Clone)]
644struct OutputCapturerer {
645 output: Arc<Mutex<mpsc::Sender<Vec<u8>>>>,
646}
647
648impl OutputCapturerer {
649 fn new() -> (Self, mpsc::Receiver<Vec<u8>>) {
650 let (tx, rx) = mpsc::channel();
651 (
652 Self {
653 output: Arc::new(Mutex::new(tx)),
654 },
655 rx,
656 )
657 }
658}
659
660impl VirtualFile for OutputCapturerer {
661 fn last_accessed(&self) -> Timestamp {
662 0
663 }
664 fn last_modified(&self) -> Timestamp {
665 0
666 }
667 fn created_time(&self) -> Timestamp {
668 0
669 }
670 fn size(&self) -> u64 {
671 0
672 }
673 fn set_len(&mut self, _new_size: Filesize) -> Result<(), FsError> {
674 Ok(())
675 }
676 fn unlink(&mut self) -> Result<(), FsError> {
677 Ok(())
678 }
679 fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
680 Poll::Ready(Ok(0))
681 }
682 fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
683 Poll::Ready(Ok(8192))
684 }
685}
686
687impl AsyncSeek for OutputCapturerer {
688 fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> {
689 Err(io::Error::other("can not seek logging wrapper"))
690 }
691 fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
692 Poll::Ready(Err(io::Error::other("can not seek logging wrapper")))
693 }
694}
695
696impl AsyncWrite for OutputCapturerer {
697 fn poll_write(
698 self: Pin<&mut Self>,
699 _cx: &mut Context<'_>,
700 buf: &[u8],
701 ) -> Poll<io::Result<usize>> {
702 self.output
703 .lock()
704 .unwrap()
705 .send(buf.to_vec())
706 .map_err(|err| io::Error::new(io::ErrorKind::BrokenPipe, err.to_string()))?;
707 Poll::Ready(Ok(buf.len()))
708 }
709 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
710 Poll::Ready(Ok(()))
711 }
712 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
713 Poll::Ready(Ok(()))
714 }
715}
716
717impl AsyncRead for OutputCapturerer {
718 fn poll_read(
719 self: Pin<&mut Self>,
720 _cx: &mut Context<'_>,
721 _buf: &mut ReadBuf<'_>,
722 ) -> Poll<io::Result<()>> {
723 Poll::Ready(Err(io::Error::other("can not read from logging wrapper")))
724 }
725}
726
727fn map_host_fs_to_mem_fs<'a>(
731 fs: &'a dyn FileSystem,
732 directory_reader: ReadDir,
733 path_prefix: &'a Path,
734) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + 'a>> {
735 Box::pin(async move {
736 for entry in directory_reader {
737 let entry = entry?;
738 let entry_type = entry.file_type()?;
739
740 let path = path_prefix.join(entry.path().file_name().unwrap());
741
742 if entry_type.is_dir() {
743 fs.create_dir(&path)?;
744
745 map_host_fs_to_mem_fs(fs, read_dir(entry.path())?, &path).await?
746 } else if entry_type.is_file() {
747 let mut host_file = OpenOptions::new().read(true).open(entry.path())?;
748 let mut mem_file = fs
749 .new_open_options()
750 .create_new(true)
751 .write(true)
752 .open(path)?;
753 let mut buffer = Vec::new();
754 Read::read_to_end(&mut host_file, &mut buffer)?;
755 mem_file.write_all(&buffer).await?;
756 } else if entry_type.is_symlink() {
757 }
759 }
760
761 Ok(())
762 })
763}