Skip to content

Commit

Permalink
fixups
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Aug 9, 2023
1 parent 33615c2 commit 294d3c5
Show file tree
Hide file tree
Showing 16 changed files with 244 additions and 165 deletions.
291 changes: 157 additions & 134 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ rustflags = ["-Zoom=panic"]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1.30.0", features = ["macros"] }
toml = "0.7"
walrus = "0.20.1"
wasm-compose = { git = "https://github.com/bytecodealliance/wasm-tools" }
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ fn main() {

When calling a subsystem for the first time, its virtualization will be enabled. Subsystems not used or configured at all will be omitted from the virtualization entirely.

### Selective Subsystem Virtualization

By default, when using the `wasi-virt` CLI command, all virtualizations are enabled. This way, not only is encapsulation the default, but composition with arbitrary components will always work out as all interfaces for WASI should always be available.

Selective subsystem virtualization can be performed directly with the WASI Virt library as above (which does not virtualize all subsystems by default). This allows virtualizing just a single subsystem like `env`, where it is possible to virtualize only that subsystem and skip other virtualizations and end up creating a smaller virtualization component.

There is an important caveat to this: _as soon as any subsystem uses IO, all subsystems using IO need to be virtualized in order to fully subclass streams and polls in the virtualization layer_. In future this caveat requirement should weaken as these features lower into the core ABI in subsequent WASI versions.

`wasm-tools compose` will error in these scenarios, and better [error messaging](https://github.com/bytecodealliance/wasm-tools/issues/1147) may be provided in future when performing invalid compositions like the above. Missing subsystems can be usually detected from the composition warnings list.

## Contributing

To build, run `./build-adapter.sh` which builds the master `virtual-adapter` component, followed by `cargo build` to build
Expand Down
Binary file modified lib/virtual_adapter.wasm
Binary file not shown.
4 changes: 1 addition & 3 deletions src/bin/wasi-virt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,11 @@ fn main() -> Result<()> {
// http
virt_opts.http(args.allow_http.unwrap_or(allow_all));

// TODO: These need completing

// random
virt_opts.random(args.allow_random.unwrap_or(allow_all));

// sockets
// virt_opts.sockets(args.allow_sockets.unwrap_or(allow_all));
virt_opts.sockets(args.allow_sockets.unwrap_or(allow_all));

// stdio
virt_opts
Expand Down
30 changes: 15 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ impl WasiVirt {
.resolve
.select_world(*pkg_id, Some("virtual-sockets"))?;

// env & exit subsystems are fully independent
// env, exit & random subsystems are fully independent
if self.env.is_some() {
bindgen.resolve.merge_worlds(env_world, base_world)?;
} else {
Expand All @@ -225,36 +225,36 @@ impl WasiVirt {
strip_io_virt(&mut module)?;
}
if let Some(clocks) = self.clocks {
if clocks {
// When subsystem is enabled, we can pass through all interfaces
// that do not rely on io. The adapter default is passthrough.
bindgen.resolve.merge_worlds(io_clocks_world, base_world)?;
} else {
// When subsystem is disabled, we must do a full virtualization
if !clocks {
// deny is effectively virtualization
// in future with fine-grained virtualization options, they
// also would extend here (ie !clocks is deceiving)
bindgen.resolve.merge_worlds(clocks_world, base_world)?;
deny_clocks_virt(&mut module)?;
} else {
// passthrough can be simplified to just rewrapping io interfaces
bindgen.resolve.merge_worlds(io_clocks_world, base_world)?;
}
} else {
strip_clocks_virt(&mut module)?;
}
// sockets and http are identical to clocks above
if let Some(sockets) = self.sockets {
if sockets {
bindgen.resolve.merge_worlds(io_sockets_world, base_world)?;
} else {
if !sockets {
bindgen.resolve.merge_worlds(sockets_world, base_world)?;
// TODO:
// deny_sockets_virt(&mut module)?;
deny_sockets_virt(&mut module)?;
} else {
bindgen.resolve.merge_worlds(io_sockets_world, base_world)?;
}
} else {
strip_sockets_virt(&mut module)?;
}
if let Some(http) = self.http {
if http {
bindgen.resolve.merge_worlds(io_http_world, base_world)?;
} else {
if !http {
bindgen.resolve.merge_worlds(http_world, base_world)?;
deny_http_virt(&mut module)?;
} else {
bindgen.resolve.merge_worlds(io_http_world, base_world)?;
}
} else {
strip_http_virt(&mut module)?;
Expand Down
3 changes: 2 additions & 1 deletion src/virt_deny.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,6 @@ pub(crate) fn deny_exit_virt(module: &mut Module) -> Result<()> {
}

pub(crate) fn deny_sockets_virt(module: &mut Module) -> Result<()> {
todo!();
// TODO: Complete stubbing implementation
Ok(())
}
5 changes: 5 additions & 0 deletions tests/cases/fs-dir-read.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ component = "file-read"

host-fs-path = "/mydir"

[virt-opts.stdio]
stdin = true
stdout = true
stderr = true

[virt-opts.fs.preopens."/".dir."mydir".dir]
"file1.txt" = { source = "inner contents1" }
"file2.txt" = { source = "inner contents2" }
Expand Down
5 changes: 5 additions & 0 deletions tests/cases/fs-file-read.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ component = "file-read"

host-fs-path = "/file.txt"

[virt-opts.stdio]
stdin = true
stdout = true
stderr = true

[virt-opts.fs.preopens."/".dir]
"file.txt" = { source = "contents" }

Expand Down
5 changes: 5 additions & 0 deletions tests/cases/fs-host-read.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ component = "file-read"

host-fs-path = "/file.txt"

[virt-opts.stdio]
stdin = true
stdout = true
stderr = true

[virt-opts.fs.preopens."/".dir]
"file.txt" = { runtime-file = "/LICENSE" }

Expand Down
5 changes: 5 additions & 0 deletions tests/cases/fs-inner-read.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ component = "file-read"

host-fs-path = "/mydir/file.txt"

[virt-opts.stdio]
stdin = true
stdout = true
stderr = true

[virt-opts.fs.preopens."/".dir."mydir".dir]
"file.txt" = { source = "inner contents" }

Expand Down
5 changes: 5 additions & 0 deletions tests/cases/fs-nested-dir-read.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ component = "file-read"

host-fs-path = "/clocks/monotonic-clock.wit"

[virt-opts.stdio]
stdin = true
stdout = true
stderr = true

[virt-opts.fs.preopens."/"]
virtualize = "./wit/deps"

Expand Down
5 changes: 5 additions & 0 deletions tests/cases/fs-passive-file-read.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ component = "file-read"

host-fs-path = "/env-none.toml"

[virt-opts.stdio]
stdin = true
stdout = true
stderr = true

[virt-opts.fs]
passive-cutoff = 10

Expand Down
5 changes: 5 additions & 0 deletions tests/cases/fs-virt-dir-read.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ component = "file-read"

host-fs-path = "/env-none.toml"

[virt-opts.stdio]
stdin = true
stdout = true
stderr = true

[virt-opts.fs.preopens."/"]
virtualize = "./tests/cases"

Expand Down
33 changes: 22 additions & 11 deletions tests/virt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ struct TestCase {
expect: TestExpectation,
}

#[async_std::test]
const DEBUG: bool = false;

#[tokio::test]
async fn virt_test() -> Result<()> {
let wasi_adapter = fs::read("lib/wasi_snapshot_preview1.reactor.wasm")?;

Expand All @@ -71,10 +73,16 @@ async fn virt_test() -> Result<()> {
let test_case_name = test_case_file_name.strip_suffix(".toml").unwrap();

// Filtering...
// if test_case_name != "encapsulate" {
// if test_case_name != "passthrough" {
// continue;
// }

if DEBUG {
if test_case_name == "encapsulate" {
continue;
}
}

println!("> {:?}", test_case_path);

// load the test case JSON data
Expand All @@ -90,12 +98,14 @@ async fn virt_test() -> Result<()> {
let mut generated_component_path = generated_path.join(component_name);
generated_component_path.set_extension("component.wasm");
cmd(&format!(
"cargo build -p {component_name} --target wasm32-wasi --release"
"cargo build -p {component_name} --target wasm32-wasi {}",
if DEBUG { "" } else { "--release" }
))?;

// encode the component
let component_core = fs::read(&format!(
"target/wasm32-wasi/release/{}.wasm",
"target/wasm32-wasi/{}/{}.wasm",
if DEBUG { "debug" } else { "release" },
component_name.to_snake_case()
))?;
let mut encoder = ComponentEncoder::default()
Expand All @@ -112,7 +122,9 @@ async fn virt_test() -> Result<()> {
virt_component_path.set_extension("virt.wasm");
let mut virt_opts = test.virt_opts.clone().unwrap_or_default();
virt_opts.exit(Default::default());
// virt_opts.wasm_opt = Some(false);
if DEBUG {
virt_opts.wasm_opt = Some(false);
}

let virt_component = virt_opts
.finish()
Expand All @@ -137,18 +149,17 @@ async fn virt_test() -> Result<()> {
}

// execute the composed virtualized component test function
let mut builder = WasiCtxBuilder::new().inherit_stdio().push_preopened_dir(
let mut builder = WasiCtxBuilder::new();
builder.inherit_stdio().preopened_dir(
Dir::open_ambient_dir(".", ambient_authority())?,
DirPerms::READ,
FilePerms::READ,
"/",
);
if let Some(host_env) = &test.host_env {
let env: Vec<(String, String)> = host_env
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
builder = builder.set_env(env.as_slice());
for (k, v) in host_env {
builder.env(k, v);
}
}
let mut table = Table::new();
let wasi = builder.build(&mut table)?;
Expand Down
2 changes: 1 addition & 1 deletion virtual-adapter/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ impl Streams for VirtAdapter {
match IoState::get_stream(sid)? {
Stream::Null => Ok((bytes.len() as u64, StreamStatus::Ended)),
Stream::StaticFile(_) | Stream::StaticDir(_) => Err(stream_err()),
Stream::Host(sid) => stream_res_map(streams::write(*sid, bytes.as_slice())),
Stream::Host(sid) => stream_res_map(streams::blocking_write(*sid, bytes.as_slice())),
}
}
fn write_zeroes(sid: u32, len: u64) -> Result<(u64, StreamStatus), StreamError> {
Expand Down

0 comments on commit 294d3c5

Please sign in to comment.