Skip to content

Commit 6a49dba

Browse files
committed
fix(wasix): Prevent recursive merge fallback when mounting packages
1 parent bb15072 commit 6a49dba

File tree

11 files changed

+228
-477
lines changed

11 files changed

+228
-477
lines changed

lib/virtual-fs/src/passthru_fs.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
//! needed so that a `Box<dyn VirtualFileSystem>` can be wrapped in an Arc and
33
//! shared - some of the interfaces pass around a `Box<dyn VirtualFileSystem>`
44
5-
use std::path::Path;
5+
use std::{path::Path, sync::Arc};
66

77
use crate::*;
88

99
#[derive(Debug)]
1010
pub struct PassthruFileSystem {
11-
fs: Box<dyn FileSystem + Send + Sync + 'static>,
11+
fs: Arc<dyn FileSystem + Send + Sync + 'static>,
1212
}
1313

1414
impl PassthruFileSystem {
15-
pub fn new(inner: Box<dyn FileSystem + Send + Sync + 'static>) -> Self {
15+
pub fn new(inner: Arc<dyn FileSystem + Send + Sync + 'static>) -> Self {
1616
Self { fs: inner }
1717
}
1818
}
@@ -66,6 +66,8 @@ impl FileSystem for PassthruFileSystem {
6666

6767
#[cfg(test)]
6868
mod test_builder {
69+
use std::sync::Arc;
70+
6971
use tokio::io::{AsyncReadExt, AsyncWriteExt};
7072

7173
use crate::{FileSystem, PassthruFileSystem};
@@ -96,7 +98,7 @@ mod test_builder {
9698
.unwrap();
9799
assert_eq!(buf, b"hello");
98100

99-
let passthru_fs = PassthruFileSystem::new(Box::new(mem_fs.clone()));
101+
let passthru_fs = PassthruFileSystem::new(Arc::new(mem_fs.clone()));
100102
let mut buf = Vec::new();
101103
passthru_fs
102104
.new_open_options()

lib/virtual-fs/src/union_fs.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::*;
88

99
use std::{path::Path, sync::Arc};
1010

11-
#[derive(Debug)]
11+
#[derive(Debug, Clone)]
1212
pub struct MountPoint {
1313
pub path: PathBuf,
1414
pub name: String,
@@ -36,6 +36,13 @@ pub struct UnionFileSystem {
3636
pub mounts: DashMap<PathBuf, MountPoint>,
3737
}
3838

39+
#[derive(Clone, Copy, Debug)]
40+
pub enum UnionMergeMode {
41+
Replace,
42+
Skip,
43+
Fail,
44+
}
45+
3946
impl UnionFileSystem {
4047
pub fn new() -> Self {
4148
Self::default()
@@ -58,6 +65,43 @@ impl UnionFileSystem {
5865
.to_owned()
5966
}
6067
}
68+
69+
pub fn merge(&self, other: &UnionFileSystem, mode: UnionMergeMode) -> Result<()> {
70+
for item in other.mounts.iter() {
71+
if self.mounts.contains_key(item.key()) {
72+
match mode {
73+
UnionMergeMode::Replace => {
74+
self.mounts.insert(item.key().clone(), item.value().clone());
75+
}
76+
UnionMergeMode::Skip => {
77+
tracing::debug!(
78+
path = %item.key().display(),
79+
"skipping existing mount point while merging two union file systems"
80+
);
81+
}
82+
UnionMergeMode::Fail => {
83+
return Err(FsError::AlreadyExists);
84+
}
85+
}
86+
} else {
87+
self.mounts.insert(item.key().clone(), item.value().clone());
88+
}
89+
}
90+
91+
Ok(())
92+
}
93+
94+
// TODO: this was only added for making BinaryPackage::webc_fs clonable,
95+
// remove once the TODO in BinaryPackage is resolved
96+
pub fn duplicate(&self) -> Self {
97+
let mounts = DashMap::new();
98+
99+
for item in self.mounts.iter() {
100+
mounts.insert(item.key().clone(), item.value().clone());
101+
}
102+
103+
Self { mounts }
104+
}
61105
}
62106

63107
impl UnionFileSystem {

lib/wasix/src/bin_factory/binary_package.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{path::Path, sync::Arc};
33
use anyhow::Context;
44
use once_cell::sync::OnceCell;
55
use sha2::Digest;
6-
use virtual_fs::FileSystem;
6+
use virtual_fs::UnionFileSystem;
77
use wasmer_config::package::{
88
PackageHash, PackageId, PackageSource, SuggestedCompilerOptimizations,
99
};
@@ -90,7 +90,10 @@ pub struct BinaryPackage {
9090
/// entrypoint.
9191
pub entrypoint_cmd: Option<String>,
9292
pub hash: OnceCell<ModuleHash>,
93-
pub webc_fs: Arc<dyn FileSystem + Send + Sync>,
93+
// TODO: using a UnionFileSystem here directly is suboptimal, since cloning
94+
// it is expensive. Shoudl instead store an immutable map that can easily
95+
// be converted into a dashmap.
96+
pub webc_fs: Option<Arc<UnionFileSystem>>,
9497
pub commands: Vec<BinaryPackageCommand>,
9598
pub uses: Vec<String>,
9699
pub file_system_memory_footprint: u64,
@@ -264,7 +267,7 @@ impl BinaryPackage {
264267
mod tests {
265268
use sha2::Digest;
266269
use tempfile::TempDir;
267-
use virtual_fs::AsyncReadExt;
270+
use virtual_fs::{AsyncReadExt, FileSystem as _};
268271
use wasmer_package::utils::from_disk;
269272

270273
use crate::{
@@ -326,6 +329,8 @@ mod tests {
326329
// "/public/file.txt" on the guest.
327330
let mut f = pkg
328331
.webc_fs
332+
.as_ref()
333+
.expect("no webc fs")
329334
.new_open_options()
330335
.read(true)
331336
.open("/public/file.txt")

lib/wasix/src/fs/mod.rs

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ use futures::{Future, TryStreamExt, future::BoxFuture};
3333
use serde_derive::{Deserialize, Serialize};
3434
use tokio::io::AsyncWriteExt;
3535
use tracing::{debug, trace};
36-
use virtual_fs::{FileSystem, FsError, OpenOptions, VirtualFile, copy_reference};
36+
use virtual_fs::{
37+
FileSystem, FsError, OpenOptions, UnionFileSystem, VirtualFile, copy_reference,
38+
tmp_fs::TmpFileSystem,
39+
};
3740
use wasmer_config::package::PackageId;
3841
use wasmer_wasix_types::{
3942
types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO},
@@ -362,100 +365,99 @@ impl Default for WasiInodes {
362365

363366
#[derive(Debug, Clone)]
364367
pub enum WasiFsRoot {
365-
Sandbox(Arc<virtual_fs::tmp_fs::TmpFileSystem>),
366-
Backing(Arc<Box<dyn FileSystem>>),
367-
}
368-
369-
impl WasiFsRoot {
370-
/// Merge the contents of a filesystem into this one.
371-
#[tracing::instrument(level = "debug", skip_all)]
372-
pub(crate) async fn merge(
373-
&self,
374-
other: &Arc<dyn FileSystem + Send + Sync>,
375-
) -> Result<(), virtual_fs::FsError> {
376-
match self {
377-
WasiFsRoot::Sandbox(fs) => {
378-
fs.union(other);
379-
Ok(())
380-
}
381-
WasiFsRoot::Backing(fs) => {
382-
merge_filesystems(other, fs).await?;
383-
Ok(())
384-
}
385-
}
386-
}
368+
Sandbox(TmpFileSystem),
369+
Overlay(Arc<virtual_fs::OverlayFileSystem<TmpFileSystem, [UnionFileSystem; 1]>>),
370+
Backing(Arc<dyn FileSystem>),
387371
}
388372

389373
impl FileSystem for WasiFsRoot {
390374
fn readlink(&self, path: &Path) -> virtual_fs::Result<PathBuf> {
391375
match self {
392-
WasiFsRoot::Sandbox(fs) => fs.readlink(path),
393-
WasiFsRoot::Backing(fs) => fs.readlink(path),
376+
Self::Sandbox(fs) => fs.readlink(path),
377+
Self::Overlay(overlay) => overlay.readlink(path),
378+
Self::Backing(fs) => fs.readlink(path),
394379
}
395380
}
396381

397382
fn read_dir(&self, path: &Path) -> virtual_fs::Result<virtual_fs::ReadDir> {
398383
match self {
399-
WasiFsRoot::Sandbox(fs) => fs.read_dir(path),
400-
WasiFsRoot::Backing(fs) => fs.read_dir(path),
384+
Self::Sandbox(fs) => fs.read_dir(path),
385+
Self::Overlay(overlay) => overlay.read_dir(path),
386+
Self::Backing(fs) => fs.read_dir(path),
401387
}
402388
}
389+
403390
fn create_dir(&self, path: &Path) -> virtual_fs::Result<()> {
404391
match self {
405-
WasiFsRoot::Sandbox(fs) => fs.create_dir(path),
406-
WasiFsRoot::Backing(fs) => fs.create_dir(path),
392+
Self::Sandbox(fs) => fs.create_dir(path),
393+
Self::Overlay(overlay) => overlay.create_dir(path),
394+
Self::Backing(fs) => fs.create_dir(path),
407395
}
408396
}
397+
409398
fn remove_dir(&self, path: &Path) -> virtual_fs::Result<()> {
410399
match self {
411-
WasiFsRoot::Sandbox(fs) => fs.remove_dir(path),
412-
WasiFsRoot::Backing(fs) => fs.remove_dir(path),
400+
Self::Sandbox(fs) => fs.remove_dir(path),
401+
Self::Overlay(overlay) => overlay.remove_dir(path),
402+
Self::Backing(fs) => fs.remove_dir(path),
413403
}
414404
}
405+
415406
fn rename<'a>(&'a self, from: &Path, to: &Path) -> BoxFuture<'a, virtual_fs::Result<()>> {
416407
let from = from.to_owned();
417408
let to = to.to_owned();
418409
let this = self.clone();
419410
Box::pin(async move {
420411
match this {
421-
WasiFsRoot::Sandbox(fs) => fs.rename(&from, &to).await,
422-
WasiFsRoot::Backing(fs) => fs.rename(&from, &to).await,
412+
Self::Sandbox(fs) => fs.rename(&from, &to).await,
413+
Self::Overlay(overlay) => overlay.rename(&from, &to).await,
414+
Self::Backing(fs) => fs.rename(&from, &to).await,
423415
}
424416
})
425417
}
418+
426419
fn metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
427420
match self {
428-
WasiFsRoot::Sandbox(fs) => fs.metadata(path),
429-
WasiFsRoot::Backing(fs) => fs.metadata(path),
421+
Self::Sandbox(fs) => fs.metadata(path),
422+
Self::Overlay(overlay) => overlay.metadata(path),
423+
Self::Backing(fs) => fs.metadata(path),
430424
}
431425
}
426+
432427
fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result<virtual_fs::Metadata> {
433428
match self {
434-
WasiFsRoot::Sandbox(fs) => fs.symlink_metadata(path),
435-
WasiFsRoot::Backing(fs) => fs.symlink_metadata(path),
429+
Self::Sandbox(fs) => fs.symlink_metadata(path),
430+
Self::Overlay(overlay) => overlay.symlink_metadata(path),
431+
Self::Backing(fs) => fs.symlink_metadata(path),
436432
}
437433
}
434+
438435
fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> {
439436
match self {
440-
WasiFsRoot::Sandbox(fs) => fs.remove_file(path),
441-
WasiFsRoot::Backing(fs) => fs.remove_file(path),
437+
Self::Sandbox(fs) => fs.remove_file(path),
438+
Self::Overlay(overlay) => overlay.remove_file(path),
439+
Self::Backing(fs) => fs.remove_file(path),
442440
}
443441
}
442+
444443
fn new_open_options(&self) -> OpenOptions<'_> {
445444
match self {
446-
WasiFsRoot::Sandbox(fs) => fs.new_open_options(),
447-
WasiFsRoot::Backing(fs) => fs.new_open_options(),
445+
Self::Sandbox(fs) => fs.new_open_options(),
446+
Self::Overlay(overlay) => overlay.new_open_options(),
447+
Self::Backing(fs) => fs.new_open_options(),
448448
}
449449
}
450+
450451
fn mount(
451452
&self,
452453
name: String,
453454
path: &Path,
454455
fs: Box<dyn FileSystem + Send + Sync>,
455456
) -> virtual_fs::Result<()> {
456457
match self {
457-
WasiFsRoot::Sandbox(f) => f.mount(name, path, fs),
458-
WasiFsRoot::Backing(f) => f.mount(name, path, fs),
458+
Self::Sandbox(root) => FileSystem::mount(root, name, path, fs),
459+
Self::Overlay(overlay) => FileSystem::mount(overlay.primary(), name, path, fs),
460+
Self::Backing(f) => f.mount(name, path, fs),
459461
}
460462
}
461463
}
@@ -638,15 +640,28 @@ impl WasiFs {
638640
&self,
639641
binary: &BinaryPackage,
640642
) -> Result<(), virtual_fs::FsError> {
641-
let needs_to_be_unioned = self.has_unioned.lock().unwrap().insert(binary.id.clone());
643+
let Some(webc_fs) = &binary.webc_fs else {
644+
return Ok(());
645+
};
642646

647+
let needs_to_be_unioned = self.has_unioned.lock().unwrap().insert(binary.id.clone());
643648
if !needs_to_be_unioned {
644649
return Ok(());
645650
}
646651

647-
self.root_fs.merge(&binary.webc_fs).await?;
648-
649-
Ok(())
652+
match &self.root_fs {
653+
WasiFsRoot::Sandbox(fs) => {
654+
// TODO: this can be changed to switch to Self::Overlay instead!
655+
let fdyn: Arc<dyn FileSystem + Send + Sync> = webc_fs.clone();
656+
fs.union(&fdyn);
657+
Ok(())
658+
}
659+
WasiFsRoot::Overlay(overlay) => {
660+
let union = &overlay.secondaries()[0];
661+
union.merge(&webc_fs, virtual_fs::UnionMergeMode::Skip)
662+
}
663+
WasiFsRoot::Backing(backing) => merge_filesystems(webc_fs, backing).await,
664+
}
650665
}
651666

652667
/// Created for the builder API. like `new` but with more information
@@ -2211,14 +2226,14 @@ impl std::fmt::Debug for WasiFs {
22112226
}
22122227

22132228
/// Returns the default filesystem backing
2214-
pub fn default_fs_backing() -> Box<dyn virtual_fs::FileSystem + Send + Sync> {
2229+
pub fn default_fs_backing() -> Arc<dyn virtual_fs::FileSystem + Send + Sync> {
22152230
cfg_if::cfg_if! {
22162231
if #[cfg(feature = "host-fs")] {
2217-
Box::new(virtual_fs::host_fs::FileSystem::new(tokio::runtime::Handle::current(), "/").unwrap())
2232+
Arc::new(virtual_fs::host_fs::FileSystem::new(tokio::runtime::Handle::current(), "/").unwrap())
22182233
} else if #[cfg(not(feature = "host-fs"))] {
2219-
Box::<virtual_fs::mem_fs::FileSystem>::default()
2234+
Arc::<virtual_fs::mem_fs::FileSystem>::default()
22202235
} else {
2221-
Box::<FallbackFileSystem>::default()
2236+
Arc::<FallbackFileSystem>::default()
22222237
}
22232238
}
22242239
}

lib/wasix/src/runners/wasi.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,12 @@ impl WasiRunner {
291291
builder.add_webc(pkg.clone());
292292
builder.set_module_hash(pkg.hash());
293293
builder.include_packages(pkg.package_ids.clone());
294-
Some(Arc::clone(&pkg.webc_fs))
294+
295+
if let Some(fs) = pkg.webc_fs.as_deref() {
296+
Some(fs.duplicate())
297+
} else {
298+
None
299+
}
295300
}
296301
PackageOrHash::Hash(hash) => {
297302
builder.set_module_hash(hash);

0 commit comments

Comments
 (0)