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