From f352ea8e20a1d45eff4217cab13edfdaa6d25694 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Mon, 8 Jul 2024 23:16:18 +0200 Subject: [PATCH] Update to wasmtime 22 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. --- crates/livesplit-auto-splitting/Cargo.toml | 8 +- crates/livesplit-auto-splitting/README.md | 5 +- crates/livesplit-auto-splitting/src/lib.rs | 5 +- .../src/runtime/api/wasi.rs | 185 ++---------------- .../src/runtime/mod.rs | 11 +- crates/livesplit-auto-splitting/src/timer.rs | 2 +- src/auto_splitting/mod.rs | 7 +- 7 files changed, 36 insertions(+), 187 deletions(-) diff --git a/crates/livesplit-auto-splitting/Cargo.toml b/crates/livesplit-auto-splitting/Cargo.toml index 5fbd61d0..bdffba8f 100644 --- a/crates/livesplit-auto-splitting/Cargo.toml +++ b/crates/livesplit-auto-splitting/Cargo.toml @@ -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"] } diff --git a/crates/livesplit-auto-splitting/README.md b/crates/livesplit-auto-splitting/README.md index 96e94b99..907739df 100644 --- a/crates/livesplit-auto-splitting/README.md +++ b/crates/livesplit-auto-splitting/README.md @@ -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 @@ -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. diff --git a/crates/livesplit-auto-splitting/src/lib.rs b/crates/livesplit-auto-splitting/src/lib.rs index 621fe334..6e81873a 100644 --- a/crates/livesplit-auto-splitting/src/lib.rs +++ b/crates/livesplit-auto-splitting/src/lib.rs @@ -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 @@ -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. diff --git a/crates/livesplit-auto-splitting/src/runtime/api/wasi.rs b/crates/livesplit-auto-splitting/src/runtime/api/wasi.rs index 133b58c0..8dd9a9d7 100644 --- a/crates/livesplit-auto-splitting/src/runtime/api/wasi.rs +++ b/crates/livesplit-auto-splitting/src/runtime/api/wasi.rs @@ -1,23 +1,15 @@ -use std::{ - path::{Path, PathBuf}, - str, -}; +use std::{path::Path, 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); } } @@ -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 { - // 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()) { + // Unfortunate if this fails, but we should still continue. + let _ = wasi.preopened_dir(path, DirPerms::READ, FilePerms::READ, "/mnt"); } - - 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> + Send>, - wasi_common::Error, - > { - self.0.readdir(cursor).await - } - - async fn read_link(&self, path: &str) -> Result { - self.0.read_link(path).await - } - - async fn get_filestat(&self) -> Result { - // 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 { - // 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 { - 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 { - let (dir, file) = device_path(path)?; - dir.read_link(file).await - } - - async fn get_path_filestat( - &self, - path: &str, - follow_symlinks: bool, - ) -> Result { - 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() } diff --git a/crates/livesplit-auto-splitting/src/runtime/mod.rs b/crates/livesplit-auto-splitting/src/runtime/mod.rs index f9466b41..ac13c4a1 100644 --- a/crates/livesplit-auto-splitting/src/runtime/mod.rs +++ b/crates/livesplit-auto-splitting/src/runtime/mod.rs @@ -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; @@ -89,7 +89,7 @@ pub struct Context { timer: T, memory: Option, process_list: ProcessList, - wasi: WasiCtx, + wasi: WasiP1Ctx, } /// A thread-safe handle used to interrupt the execution of the script. @@ -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 diff --git a/crates/livesplit-auto-splitting/src/timer.rs b/crates/livesplit-auto-splitting/src/timer.rs index d5052a5e..96258b7b 100644 --- a/crates/livesplit-auto-splitting/src/timer.rs +++ b/crates/livesplit-auto-splitting/src/timer.rs @@ -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. diff --git a/src/auto_splitting/mod.rs b/src/auto_splitting/mod.rs index 5b665196..32b55701 100644 --- a/src/auto_splitting/mod.rs +++ b/src/auto_splitting/mod.rs @@ -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. @@ -719,7 +718,7 @@ impl Runtime { // is an Arc>, so we can't implement the trait directly on it. struct Timer(E); -impl AutoSplitTimer for Timer { +impl AutoSplitTimer for Timer { fn state(&self) -> TimerState { match self.0.get_timer().current_phase() { TimerPhase::NotRunning => TimerState::NotRunning, @@ -770,7 +769,7 @@ impl AutoSplitTimer for Timer { } } -async fn run( +async fn run( mut auto_splitter: watch::Receiver>>>, timeout_sender: watch::Sender>, interrupt_sender: watch::Sender>,