1mod io;
2pub mod spin;
3mod wasi_2023_10_18;
4mod wasi_2023_11_10;
5
6use std::{
7 future::Future,
8 io::{Read, Write},
9 net::SocketAddr,
10 path::Path,
11};
12
13use io::{PipeReadStream, PipedWriteStream};
14use spin_factors::{
15 anyhow, AppComponent, Factor, FactorInstanceBuilder, InitContext, PrepareContext,
16 RuntimeFactors, RuntimeFactorsInstanceState,
17};
18use wasmtime_wasi::{
19 DirPerms, FilePerms, IoImpl, IoView, ResourceTable, StdinStream, StdoutStream, WasiCtx,
20 WasiCtxBuilder, WasiImpl, WasiView,
21};
22
23pub use wasmtime_wasi::SocketAddrUse;
24
25pub struct WasiFactor {
26 files_mounter: Box<dyn FilesMounter>,
27}
28
29impl WasiFactor {
30 pub fn new(files_mounter: impl FilesMounter + 'static) -> Self {
31 Self {
32 files_mounter: Box::new(files_mounter),
33 }
34 }
35
36 pub fn get_wasi_impl(
37 runtime_instance_state: &mut impl RuntimeFactorsInstanceState,
38 ) -> Option<WasiImpl<impl WasiView + '_>> {
39 let (state, table) = runtime_instance_state.get_with_table::<WasiFactor>()?;
40 Some(WasiImpl(IoImpl(WasiImplInner {
41 ctx: &mut state.ctx,
42 table,
43 })))
44 }
45}
46
47impl Factor for WasiFactor {
48 type RuntimeConfig = ();
49 type AppState = ();
50 type InstanceBuilder = InstanceBuilder;
51
52 fn init<T: Send + 'static>(&mut self, mut ctx: InitContext<T, Self>) -> anyhow::Result<()> {
53 fn type_annotate_wasi<T, F>(f: F) -> F
54 where
55 F: Fn(&mut T) -> WasiImpl<WasiImplInner>,
56 {
57 f
58 }
59 fn type_annotate_io<T, F>(f: F) -> F
60 where
61 F: Fn(&mut T) -> IoImpl<WasiImplInner>,
62 {
63 f
64 }
65 let get_data_with_table = ctx.get_data_with_table_fn();
66 let io_closure = type_annotate_io(move |data| {
67 let (state, table) = get_data_with_table(data);
68 IoImpl(WasiImplInner {
69 ctx: &mut state.ctx,
70 table,
71 })
72 });
73 let wasi_closure = type_annotate_wasi(move |data| WasiImpl(io_closure(data)));
74 let linker = ctx.linker();
75 use wasmtime_wasi::bindings;
76 bindings::clocks::wall_clock::add_to_linker_get_host(linker, wasi_closure)?;
77 bindings::clocks::monotonic_clock::add_to_linker_get_host(linker, wasi_closure)?;
78 bindings::filesystem::types::add_to_linker_get_host(linker, wasi_closure)?;
79 bindings::filesystem::preopens::add_to_linker_get_host(linker, wasi_closure)?;
80 bindings::io::error::add_to_linker_get_host(linker, io_closure)?;
81 bindings::io::poll::add_to_linker_get_host(linker, io_closure)?;
82 bindings::io::streams::add_to_linker_get_host(linker, io_closure)?;
83 bindings::random::random::add_to_linker_get_host(linker, wasi_closure)?;
84 bindings::random::insecure::add_to_linker_get_host(linker, wasi_closure)?;
85 bindings::random::insecure_seed::add_to_linker_get_host(linker, wasi_closure)?;
86 bindings::cli::exit::add_to_linker_get_host(linker, &Default::default(), wasi_closure)?;
87 bindings::cli::environment::add_to_linker_get_host(linker, wasi_closure)?;
88 bindings::cli::stdin::add_to_linker_get_host(linker, wasi_closure)?;
89 bindings::cli::stdout::add_to_linker_get_host(linker, wasi_closure)?;
90 bindings::cli::stderr::add_to_linker_get_host(linker, wasi_closure)?;
91 bindings::cli::terminal_input::add_to_linker_get_host(linker, wasi_closure)?;
92 bindings::cli::terminal_output::add_to_linker_get_host(linker, wasi_closure)?;
93 bindings::cli::terminal_stdin::add_to_linker_get_host(linker, wasi_closure)?;
94 bindings::cli::terminal_stdout::add_to_linker_get_host(linker, wasi_closure)?;
95 bindings::cli::terminal_stderr::add_to_linker_get_host(linker, wasi_closure)?;
96 bindings::sockets::tcp::add_to_linker_get_host(linker, wasi_closure)?;
97 bindings::sockets::tcp_create_socket::add_to_linker_get_host(linker, wasi_closure)?;
98 bindings::sockets::udp::add_to_linker_get_host(linker, wasi_closure)?;
99 bindings::sockets::udp_create_socket::add_to_linker_get_host(linker, wasi_closure)?;
100 bindings::sockets::instance_network::add_to_linker_get_host(linker, wasi_closure)?;
101 bindings::sockets::network::add_to_linker_get_host(
102 linker,
103 &Default::default(),
104 wasi_closure,
105 )?;
106 bindings::sockets::ip_name_lookup::add_to_linker_get_host(linker, wasi_closure)?;
107
108 wasi_2023_10_18::add_to_linker(linker, wasi_closure)?;
109 wasi_2023_11_10::add_to_linker(linker, wasi_closure)?;
110
111 Ok(())
112 }
113
114 fn configure_app<T: RuntimeFactors>(
115 &self,
116 _ctx: spin_factors::ConfigureAppContext<T, Self>,
117 ) -> anyhow::Result<Self::AppState> {
118 Ok(())
119 }
120
121 fn prepare<T: RuntimeFactors>(
122 &self,
123 ctx: PrepareContext<T, Self>,
124 ) -> anyhow::Result<InstanceBuilder> {
125 let mut wasi_ctx = WasiCtxBuilder::new();
126
127 let mount_ctx = MountFilesContext { ctx: &mut wasi_ctx };
129 self.files_mounter
130 .mount_files(ctx.app_component(), mount_ctx)?;
131
132 let mut builder = InstanceBuilder { ctx: wasi_ctx };
133
134 builder.env(ctx.app_component().environment());
136
137 Ok(builder)
138 }
139}
140
141pub trait FilesMounter: Send + Sync {
142 fn mount_files(
143 &self,
144 app_component: &AppComponent,
145 ctx: MountFilesContext,
146 ) -> anyhow::Result<()>;
147}
148
149pub struct DummyFilesMounter;
150
151impl FilesMounter for DummyFilesMounter {
152 fn mount_files(
153 &self,
154 app_component: &AppComponent,
155 _ctx: MountFilesContext,
156 ) -> anyhow::Result<()> {
157 anyhow::ensure!(
158 app_component.files().next().is_none(),
159 "DummyFilesMounter can't actually mount files"
160 );
161 Ok(())
162 }
163}
164
165pub struct MountFilesContext<'a> {
166 ctx: &'a mut WasiCtxBuilder,
167}
168
169impl MountFilesContext<'_> {
170 pub fn preopened_dir(
171 &mut self,
172 host_path: impl AsRef<Path>,
173 guest_path: impl AsRef<str>,
174 writable: bool,
175 ) -> anyhow::Result<()> {
176 let (dir_perms, file_perms) = if writable {
177 (DirPerms::all(), FilePerms::all())
178 } else {
179 (DirPerms::READ, FilePerms::READ)
180 };
181 self.ctx
182 .preopened_dir(host_path, guest_path, dir_perms, file_perms)?;
183 Ok(())
184 }
185}
186
187pub struct InstanceBuilder {
188 ctx: WasiCtxBuilder,
189}
190
191impl InstanceBuilder {
192 pub fn stdin(&mut self, stdin: impl StdinStream + 'static) {
194 self.ctx.stdin(stdin);
195 }
196
197 pub fn stdin_pipe(&mut self, r: impl Read + Send + Sync + Unpin + 'static) {
199 self.stdin(PipeReadStream::new(r));
200 }
201
202 pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) {
204 self.ctx.stdout(stdout);
205 }
206
207 pub fn stdout_pipe(&mut self, w: impl Write + Send + Sync + Unpin + 'static) {
209 self.stdout(PipedWriteStream::new(w));
210 }
211
212 pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) {
214 self.ctx.stderr(stderr);
215 }
216
217 pub fn stderr_pipe(&mut self, w: impl Write + Send + Sync + Unpin + 'static) {
219 self.stderr(PipedWriteStream::new(w));
220 }
221
222 pub fn args(&mut self, args: impl IntoIterator<Item = impl AsRef<str>>) {
224 for arg in args {
225 self.ctx.arg(arg);
226 }
227 }
228
229 pub fn env(&mut self, vars: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>)>) {
231 for (k, v) in vars {
232 self.ctx.env(k, v);
233 }
234 }
235
236 pub fn preopened_dir(
239 &mut self,
240 host_path: impl AsRef<Path>,
241 guest_path: impl AsRef<str>,
242 writable: bool,
243 ) -> anyhow::Result<()> {
244 let (dir_perms, file_perms) = if writable {
245 (DirPerms::all(), FilePerms::all())
246 } else {
247 (DirPerms::READ, FilePerms::READ)
248 };
249 self.ctx
250 .preopened_dir(host_path, guest_path, dir_perms, file_perms)?;
251 Ok(())
252 }
253}
254
255impl FactorInstanceBuilder for InstanceBuilder {
256 type InstanceState = InstanceState;
257
258 fn build(self) -> anyhow::Result<Self::InstanceState> {
259 let InstanceBuilder { ctx: mut wasi_ctx } = self;
260 Ok(InstanceState {
261 ctx: wasi_ctx.build(),
262 })
263 }
264}
265
266impl InstanceBuilder {
267 pub fn outbound_socket_addr_check<F, Fut>(&mut self, check: F)
268 where
269 F: Fn(SocketAddr, SocketAddrUse) -> Fut + Send + Sync + Clone + 'static,
270 Fut: Future<Output = bool> + Send + Sync,
271 {
272 self.ctx.socket_addr_check(move |addr, addr_use| {
273 let check = check.clone();
274 Box::pin(async move {
275 match addr_use {
276 wasmtime_wasi::SocketAddrUse::TcpBind => false,
277 wasmtime_wasi::SocketAddrUse::TcpConnect
278 | wasmtime_wasi::SocketAddrUse::UdpBind
279 | wasmtime_wasi::SocketAddrUse::UdpConnect
280 | wasmtime_wasi::SocketAddrUse::UdpOutgoingDatagram => {
281 check(addr, addr_use).await
282 }
283 }
284 })
285 });
286 }
287}
288
289pub struct InstanceState {
290 ctx: WasiCtx,
291}
292
293struct WasiImplInner<'a> {
294 ctx: &'a mut WasiCtx,
295 table: &'a mut ResourceTable,
296}
297
298impl WasiView for WasiImplInner<'_> {
299 fn ctx(&mut self) -> &mut WasiCtx {
300 self.ctx
301 }
302}
303
304impl IoView for WasiImplInner<'_> {
305 fn table(&mut self) -> &mut ResourceTable {
306 self.table
307 }
308}