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>,