Skip to content

Commit

Permalink
Update to wasmtime 22
Browse files Browse the repository at this point in the history
This updates us to the latest version of wasmtime. The WASI API changed
a lot, because WASI 0.2 got released since then. We still only support
WASI 0.1 until there's practical toolchains for targeting WASI 0.2, so
we can experiment with it.

Unfortunately the new API does not allow us to define custom file system
logic anymore, so mapping to Windows device paths is not possible
anymore.
  • Loading branch information
CryZe committed Jul 8, 2024
1 parent bb8cb41 commit f352ea8
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 187 deletions.
8 changes: 4 additions & 4 deletions crates/livesplit-auto-splitting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ sysinfo = { version = "0.30.0", default-features = false, features = [
"multithread",
] }
time = { version = "0.3.3", default-features = false }
wasmtime = { version = "17.0.0", default-features = false, features = [
wasmtime = { version = "22.0.0", default-features = false, features = [
"cranelift",
"parallel-compilation",
"runtime",
] }
wasmtime-wasi = { version = "17.0.0", default-features = false, features = [
"sync",
wasmtime-wasi = { version = "22.0.0", default-features = false, features = [
"preview1",
] }
wasi-common = "17.0.0"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52.0", features = ["Win32_Storage_FileSystem"] }
Expand Down
5 changes: 2 additions & 3 deletions crates/livesplit-auto-splitting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ extern "C" {
list_ptr: *mut ProcessId,
list_len_ptr: *mut usize,
) -> bool;
/// Checks whether is a process is still open. You should detach from a
/// Checks whether a process is still open. You should detach from a
/// process and stop using it if this returns `false`.
pub fn process_is_open(process: Process) -> bool;
/// Reads memory from a process at the address given. This will write
Expand Down Expand Up @@ -534,8 +534,7 @@ support:
nothing.
- The file system is currently almost entirely empty. The host's file system is
accessible through `/mnt`. It is entirely read-only. Windows paths are mapped
to `/mnt/c`, `/mnt/d`, etc. to match WSL. Additionally `/mnt/device` maps to
`\\?\` on Windows to access additional paths.
to `/mnt/c`, `/mnt/d`, etc. to match WSL.
- There are no environment variables.
- There are no command line arguments.
- There is no networking.
Expand Down
5 changes: 2 additions & 3 deletions crates/livesplit-auto-splitting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
//! list_ptr: *mut ProcessId,
//! list_len_ptr: *mut usize,
//! ) -> bool;
//! /// Checks whether is a process is still open. You should detach from a
//! /// Checks whether a process is still open. You should detach from a
//! /// process and stop using it if this returns `false`.
//! pub fn process_is_open(process: Process) -> bool;
//! /// Reads memory from a process at the address given. This will write
Expand Down Expand Up @@ -534,8 +534,7 @@
//! nothing.
//! - The file system is currently almost entirely empty. The host's file system
//! is accessible through `/mnt`. It is entirely read-only. Windows paths are
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL. Additionally
//! `/mnt/device` maps to `\\?\` on Windows to access additional paths.
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL.
//! - There are no environment variables.
//! - There are no command line arguments.
//! - There is no networking.
Expand Down
185 changes: 20 additions & 165 deletions crates/livesplit-auto-splitting/src/runtime/api/wasi.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
use std::{
path::{Path, PathBuf},
str,
};
use std::{path::Path, str};

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Run benchmarks

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64 musl)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux Beta)

unused import: `str`

use wasi_common::{
dir::{OpenResult, ReaddirCursor, ReaddirEntity},
file::{FdFlags, Filestat, OFlags},
ErrorExt, WasiCtx, WasiDir,
};
use wasmtime_wasi::{ambient_authority, WasiCtxBuilder};
use wasmtime_wasi::{preview1::WasiP1Ctx, DirPerms, FilePerms, WasiCtxBuilder};

use crate::wasi_path;

pub fn build(script_path: Option<&Path>) -> WasiCtx {
let mut wasi = WasiCtxBuilder::new().build();
pub fn build(script_path: Option<&Path>) -> WasiP1Ctx {
let mut wasi = WasiCtxBuilder::new();

if let Some(script_path) = script_path {
if let Some(path) = wasi_path::from_native(script_path) {
let _ = wasi.push_env("SCRIPT_PATH", &path);
wasi.env("SCRIPT_PATH", &path);
}
}

Expand All @@ -32,164 +24,27 @@ pub fn build(script_path: Option<&Path>) -> WasiCtx {
}
drives &= !(1 << drive_idx);
let drive = drive_idx as u8 + b'a';
if let Ok(path) = wasmtime_wasi::Dir::open_ambient_dir(
// Unfortunate if this fails, but we should still continue.
let _ = wasi.preopened_dir(
str::from_utf8(&[b'\\', b'\\', b'?', b'\\', drive, b':', b'\\']).unwrap(),
ambient_authority(),
) {
wasi.push_dir(
Box::new(ReadOnlyDir(wasmtime_wasi::dir::Dir::from_cap_std(path))),
PathBuf::from(str::from_utf8(&[b'/', b'm', b'n', b't', b'/', drive]).unwrap()),
)
.unwrap();
}
str::from_utf8(&[b'/', b'm', b'n', b't', b'/', drive]).unwrap(),
DirPerms::READ,
FilePerms::READ,
);
}

wasi.push_dir(Box::new(DeviceDir), PathBuf::from("/mnt/device"))
.unwrap();
// FIXME: Unfortunately wasmtime doesn't support us defining our own
// file system logic anymore.

// wasi.push_dir(Box::new(DeviceDir), PathBuf::from("/mnt/device"))
// .unwrap();
}
#[cfg(not(windows))]
{
if let Ok(path) = wasmtime_wasi::Dir::open_ambient_dir("/", ambient_authority()) {
wasi.push_dir(
Box::new(ReadOnlyDir(wasmtime_wasi::dir::Dir::from_cap_std(path))),
PathBuf::from("/mnt"),
)
.unwrap();
}
}
wasi
}

struct ReadOnlyDir(wasmtime_wasi::dir::Dir);

#[async_trait::async_trait]
impl WasiDir for ReadOnlyDir {
fn as_any(&self) -> &dyn std::any::Any {
self
}

async fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
read: bool,
write: bool,
fdflags: FdFlags,
) -> Result<OpenResult, wasi_common::Error> {
// We whitelist the OFlags and FdFlags to not accidentally allow
// ways to modify the file system.
const WHITELISTED_O_FLAGS: OFlags = OFlags::DIRECTORY;
const WHITELISTED_FD_FLAGS: FdFlags = FdFlags::NONBLOCK;

if write || !WHITELISTED_O_FLAGS.contains(oflags) || !WHITELISTED_FD_FLAGS.contains(fdflags)
{
return Err(wasi_common::Error::not_supported());
if let Ok(path) = Dir::open_ambient_dir("/", ambient_authority()) {

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

failed to resolve: use of undeclared type `Dir`

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

cannot find function `ambient_authority` in this scope

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Run benchmarks

failed to resolve: use of undeclared type `Dir`

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Run benchmarks

cannot find function `ambient_authority` in this scope

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64)

failed to resolve: use of undeclared type `Dir`

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64)

cannot find function `ambient_authority` in this scope

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64 musl)

failed to resolve: use of undeclared type `Dir`

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64 musl)

cannot find function `ambient_authority` in this scope

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux Beta)

failed to resolve: use of undeclared type `Dir`

Check failure on line 44 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux Beta)

cannot find function `ambient_authority` in this scope
// Unfortunate if this fails, but we should still continue.
let _ = wasi.preopened_dir(path, DirPerms::READ, FilePerms::READ, "/mnt");

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

the trait bound `wasmtime_wasi::DirPerms: std::convert::AsRef<str>` is not satisfied

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

arguments to this method are incorrect

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Run benchmarks

the trait bound `DirPerms: AsRef<str>` is not satisfied

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Run benchmarks

arguments to this method are incorrect

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64)

the trait bound `DirPerms: AsRef<str>` is not satisfied

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64)

arguments to this method are incorrect

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64 musl)

the trait bound `DirPerms: AsRef<str>` is not satisfied

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64 musl)

arguments to this method are incorrect

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux Beta)

the trait bound `DirPerms: AsRef<str>` is not satisfied

Check failure on line 46 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux Beta)

arguments to this method are incorrect
}

Ok(
match self
.0
.open_file_(symlink_follow, path, oflags, read, write, fdflags)?
{
wasmtime_wasi::dir::OpenResult::Dir(d) => OpenResult::Dir(Box::new(ReadOnlyDir(d))),
// We assume that wrapping the file type itself is not
// necessary, because we ensure that the open flags don't allow
// for any modifications anyway.
wasmtime_wasi::dir::OpenResult::File(f) => OpenResult::File(Box::new(f)),
},
)
}

async fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<
Box<dyn Iterator<Item = Result<ReaddirEntity, wasi_common::Error>> + Send>,
wasi_common::Error,
> {
self.0.readdir(cursor).await
}

async fn read_link(&self, path: &str) -> Result<PathBuf, wasi_common::Error> {
self.0.read_link(path).await
}

async fn get_filestat(&self) -> Result<Filestat, wasi_common::Error> {
// FIXME: Make sure this says it's readonly, if it ever contains the
// permissions.
self.0.get_filestat().await
}

async fn get_path_filestat(
&self,
path: &str,
follow_symlinks: bool,
) -> Result<Filestat, wasi_common::Error> {
// FIXME: Make sure this says it's readonly, if it ever contains the
// permissions.
self.0.get_path_filestat(path, follow_symlinks).await
}
}

#[cfg(windows)]
struct DeviceDir;

#[cfg(windows)]
#[async_trait::async_trait]
impl WasiDir for DeviceDir {
fn as_any(&self) -> &dyn std::any::Any {
self
}

async fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
read: bool,
write: bool,
fdflags: FdFlags,
) -> Result<OpenResult, wasi_common::Error> {
let (dir, file) = device_path(path)?;
dir.open_file(symlink_follow, file, oflags, read, write, fdflags)
.await
}

// FIXME: cap-primitives/src/windows/fs/get_path tries to strip `\\?\`,
// which breaks paths that aren't valid without it, such as UNC paths:
// https://github.com/bytecodealliance/cap-std/issues/348

async fn read_link(&self, path: &str) -> Result<PathBuf, wasi_common::Error> {
let (dir, file) = device_path(path)?;
dir.read_link(file).await
}

async fn get_path_filestat(
&self,
path: &str,
follow_symlinks: bool,
) -> Result<Filestat, wasi_common::Error> {
let (dir, file) = device_path(path)?;
dir.get_path_filestat(file, follow_symlinks).await
}
}

#[cfg(windows)]
fn device_path(path: &str) -> Result<(ReadOnlyDir, &str), wasi_common::Error> {
let (parent, file) = path
.strip_suffix('/')
.unwrap_or(path)
.rsplit_once('/')
.ok_or_else(wasi_common::Error::not_supported)?;

let parent = wasi_path::to_native(&format!("/mnt/device/{parent}"), true)
.ok_or_else(wasi_common::Error::not_supported)?;

let dir = wasmtime_wasi::dir::Dir::from_cap_std(
wasmtime_wasi::Dir::open_ambient_dir(parent, ambient_authority())
.map_err(|_| wasi_common::Error::not_supported())?,
);

Ok((ReadOnlyDir(dir), file))
wasi.build_p1()
}
11 changes: 4 additions & 7 deletions crates/livesplit-auto-splitting/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
use wasmtime::{
Engine, Extern, Linker, Memory, Module, OptLevel, Store, TypedFunc, WasmBacktraceDetails,
};
use wasmtime_wasi::WasiCtx;
use wasmtime_wasi::preview1::WasiP1Ctx;

mod api;

Expand Down Expand Up @@ -89,7 +89,7 @@ pub struct Context<T> {
timer: T,
memory: Option<Memory>,
process_list: ProcessList,
wasi: WasiCtx,
wasi: WasiP1Ctx,
}

/// A thread-safe handle used to interrupt the execution of the script.
Expand Down Expand Up @@ -396,11 +396,8 @@ impl CompiledAutoSplitter {
.any(|import| import.module() == "wasi_snapshot_preview1");

if uses_wasi {
wasmtime_wasi::snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(
&mut linker,
|ctx| &mut ctx.wasi,
)
.map_err(|source| CreationError::Wasi { source })?;
wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |ctx| &mut ctx.wasi)
.map_err(|source| CreationError::Wasi { source })?;
}

let instance = linker
Expand Down
2 changes: 1 addition & 1 deletion crates/livesplit-auto-splitting/src/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub enum TimerState {
}

/// A timer that can be controlled by an auto splitter.
pub trait Timer {
pub trait Timer: Send {
/// Returns the current state of the timer.
fn state(&self) -> TimerState;
/// Starts the timer.
Expand Down
7 changes: 3 additions & 4 deletions src/auto_splitting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,7 @@
//! nothing.
//! - The file system is currently almost entirely empty. The host's file system
//! is accessible through `/mnt`. It is entirely read-only. Windows paths are
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL. Additionally
//! `/mnt/device` maps to `\\?\` on Windows to access additional paths.
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL.
//! - There are no environment variables.
//! - There are no command line arguments.
//! - There is no networking.
Expand Down Expand Up @@ -719,7 +718,7 @@ impl<T: event::CommandSink + TimerQuery + Send + 'static> Runtime<T> {
// is an Arc<RwLock<T>>, so we can't implement the trait directly on it.
struct Timer<E>(E);

impl<E: event::CommandSink + TimerQuery> AutoSplitTimer for Timer<E> {
impl<E: event::CommandSink + TimerQuery + Send> AutoSplitTimer for Timer<E> {
fn state(&self) -> TimerState {
match self.0.get_timer().current_phase() {
TimerPhase::NotRunning => TimerState::NotRunning,
Expand Down Expand Up @@ -770,7 +769,7 @@ impl<E: event::CommandSink + TimerQuery> AutoSplitTimer for Timer<E> {
}
}

async fn run<T: event::CommandSink + TimerQuery>(
async fn run<T: event::CommandSink + TimerQuery + Send>(
mut auto_splitter: watch::Receiver<Option<AutoSplitter<Timer<T>>>>,
timeout_sender: watch::Sender<Option<Instant>>,
interrupt_sender: watch::Sender<Option<InterruptHandle>>,
Expand Down

0 comments on commit f352ea8

Please sign in to comment.