1mod io;
2pub mod sockets;
3pub mod spin;
4mod wasi_2023_10_18;
5mod wasi_2023_11_10;
6
7use std::{
8 future::Future,
9 io::{Read, Write},
10 net::SocketAddr,
11 path::Path,
12 sync::Arc,
13};
14
15use io::{PipeReadStream, PipedWriteStream};
16use spin_factors::{
17 AppComponent, Factor, FactorInstanceBuilder, InitContext, PrepareContext, RuntimeFactors,
18 RuntimeFactorsInstanceState, anyhow,
19};
20use wasmtime::component::HasData;
21use wasmtime_wasi::cli::{StdinStream, StdoutStream, WasiCli, WasiCliCtxView};
22use wasmtime_wasi::clocks::{WasiClocks, WasiClocksCtxView};
23use wasmtime_wasi::filesystem::{WasiFilesystem, WasiFilesystemCtxView};
24use wasmtime_wasi::random::{WasiRandom, WasiRandomCtx};
25use wasmtime_wasi::sockets::{WasiSockets, WasiSocketsCtxView};
26use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView};
27
28pub use sockets::{SocketPermitState, SpinSockets, SpinSocketsView};
29pub use wasmtime_wasi::sockets::SocketAddrUse;
30
31pub struct WasiFactor {
32 files_mounter: Box<dyn FilesMounter>,
33}
34
35impl WasiFactor {
36 pub fn new(files_mounter: impl FilesMounter + 'static) -> Self {
37 Self {
38 files_mounter: Box::new(files_mounter),
39 }
40 }
41
42 pub fn get_wasi_impl(
43 runtime_instance_state: &mut impl RuntimeFactorsInstanceState,
44 ) -> Option<WasiCtxView<'_>> {
45 let (state, table) = runtime_instance_state.get_with_table::<WasiFactor>()?;
46 Some(WasiCtxView {
47 ctx: &mut state.ctx,
48 table,
49 })
50 }
51
52 pub fn get_cli_impl(
53 runtime_instance_state: &mut impl RuntimeFactorsInstanceState,
54 ) -> Option<WasiCliCtxView<'_>> {
55 let (state, table) = runtime_instance_state.get_with_table::<WasiFactor>()?;
56 Some(WasiCliCtxView {
57 ctx: state.ctx.cli(),
58 table,
59 })
60 }
61
62 pub fn get_sockets_impl(
63 runtime_instance_state: &mut impl RuntimeFactorsInstanceState,
64 ) -> Option<SpinSocketsView<'_>> {
65 let (state, table) = runtime_instance_state.get_with_table::<WasiFactor>()?;
66 Some(SpinSocketsView {
67 inner: WasiSocketsCtxView {
68 ctx: state.ctx.sockets(),
69 table,
70 },
71 permit_state: state.socket_permit_state.clone(),
72 })
73 }
74}
75
76#[allow(clippy::type_complexity, reason = "sorry, blame alex")]
80trait InitContextExt: InitContext<WasiFactor> {
81 fn get_table(data: &mut Self::StoreData) -> &mut ResourceTable {
82 let (_state, table) = Self::get_data_with_table(data);
83 table
84 }
85
86 fn get_clocks(data: &mut Self::StoreData) -> WasiClocksCtxView<'_> {
87 let (state, table) = Self::get_data_with_table(data);
88 WasiClocksCtxView {
89 ctx: state.ctx.clocks(),
90 table,
91 }
92 }
93
94 fn get_random(data: &mut Self::StoreData) -> &mut WasiRandomCtx {
95 let (state, _) = Self::get_data_with_table(data);
96 state.ctx.random()
97 }
98
99 fn link_clocks_bindings(
100 &mut self,
101 add_to_linker: fn(
102 &mut wasmtime::component::Linker<Self::StoreData>,
103 fn(&mut Self::StoreData) -> WasiClocksCtxView<'_>,
104 ) -> wasmtime::Result<()>,
105 ) -> wasmtime::Result<()> {
106 add_to_linker(self.linker(), Self::get_clocks)
107 }
108
109 fn get_cli(data: &mut Self::StoreData) -> WasiCliCtxView<'_> {
110 let (state, table) = Self::get_data_with_table(data);
111 WasiCliCtxView {
112 ctx: state.ctx.cli(),
113 table,
114 }
115 }
116
117 fn link_cli_bindings(
118 &mut self,
119 add_to_linker: fn(
120 &mut wasmtime::component::Linker<Self::StoreData>,
121 fn(&mut Self::StoreData) -> WasiCliCtxView<'_>,
122 ) -> wasmtime::Result<()>,
123 ) -> wasmtime::Result<()> {
124 add_to_linker(self.linker(), Self::get_cli)
125 }
126
127 fn link_cli_default_bindings<O: Default>(
128 &mut self,
129 add_to_linker: fn(
130 &mut wasmtime::component::Linker<Self::StoreData>,
131 &O,
132 fn(&mut Self::StoreData) -> WasiCliCtxView<'_>,
133 ) -> wasmtime::Result<()>,
134 ) -> wasmtime::Result<()> {
135 add_to_linker(self.linker(), &O::default(), Self::get_cli)
136 }
137
138 fn get_filesystem(data: &mut Self::StoreData) -> WasiFilesystemCtxView<'_> {
139 let (state, table) = Self::get_data_with_table(data);
140 WasiFilesystemCtxView {
141 ctx: state.ctx.filesystem(),
142 table,
143 }
144 }
145
146 fn link_filesystem_bindings(
147 &mut self,
148 add_to_linker: fn(
149 &mut wasmtime::component::Linker<Self::StoreData>,
150 fn(&mut Self::StoreData) -> WasiFilesystemCtxView<'_>,
151 ) -> wasmtime::Result<()>,
152 ) -> wasmtime::Result<()> {
153 add_to_linker(self.linker(), Self::get_filesystem)
154 }
155
156 fn get_sockets(data: &mut Self::StoreData) -> WasiSocketsCtxView<'_> {
157 let (state, table) = Self::get_data_with_table(data);
158 WasiSocketsCtxView {
159 ctx: state.ctx.sockets(),
160 table,
161 }
162 }
163
164 fn link_sockets_bindings(
165 &mut self,
166 add_to_linker: fn(
167 &mut wasmtime::component::Linker<Self::StoreData>,
168 fn(&mut Self::StoreData) -> WasiSocketsCtxView<'_>,
169 ) -> wasmtime::Result<()>,
170 ) -> wasmtime::Result<()> {
171 add_to_linker(self.linker(), Self::get_sockets)
172 }
173
174 fn link_sockets_default_bindings<O: Default>(
175 &mut self,
176 add_to_linker: fn(
177 &mut wasmtime::component::Linker<Self::StoreData>,
178 &O,
179 fn(&mut Self::StoreData) -> WasiSocketsCtxView<'_>,
180 ) -> wasmtime::Result<()>,
181 ) -> wasmtime::Result<()> {
182 add_to_linker(self.linker(), &O::default(), Self::get_sockets)
183 }
184
185 fn get_spin_sockets(data: &mut Self::StoreData) -> SpinSocketsView<'_> {
186 let (state, table) = Self::get_data_with_table(data);
187 SpinSocketsView {
188 inner: WasiSocketsCtxView {
189 ctx: state.ctx.sockets(),
190 table,
191 },
192 permit_state: state.socket_permit_state.clone(),
193 }
194 }
195
196 fn link_spin_sockets_bindings(
197 &mut self,
198 add_to_linker: fn(
199 &mut wasmtime::component::Linker<Self::StoreData>,
200 fn(&mut Self::StoreData) -> SpinSocketsView<'_>,
201 ) -> wasmtime::Result<()>,
202 ) -> wasmtime::Result<()> {
203 add_to_linker(self.linker(), Self::get_spin_sockets)
204 }
205
206 fn link_io_bindings(
207 &mut self,
208 add_to_linker: fn(
209 &mut wasmtime::component::Linker<Self::StoreData>,
210 fn(&mut Self::StoreData) -> &mut ResourceTable,
211 ) -> wasmtime::Result<()>,
212 ) -> wasmtime::Result<()> {
213 add_to_linker(self.linker(), Self::get_table)
214 }
215
216 fn link_random_bindings(
217 &mut self,
218 add_to_linker: fn(
219 &mut wasmtime::component::Linker<Self::StoreData>,
220 fn(&mut Self::StoreData) -> &mut WasiRandomCtx,
221 ) -> wasmtime::Result<()>,
222 ) -> wasmtime::Result<()> {
223 add_to_linker(self.linker(), |data| {
224 let (state, _table) = Self::get_data_with_table(data);
225 state.ctx.random()
226 })
227 }
228
229 fn link_all_bindings(
230 &mut self,
231 add_to_linker: fn(
232 &mut wasmtime::component::Linker<Self::StoreData>,
233 fn(&mut Self::StoreData) -> &mut ResourceTable,
234 fn(&mut Self::StoreData) -> &mut WasiRandomCtx,
235 fn(&mut Self::StoreData) -> WasiClocksCtxView<'_>,
236 fn(&mut Self::StoreData) -> WasiCliCtxView<'_>,
237 fn(&mut Self::StoreData) -> WasiFilesystemCtxView<'_>,
238 fn(&mut Self::StoreData) -> SpinSocketsView<'_>,
239 ) -> anyhow::Result<()>,
240 ) -> anyhow::Result<()> {
241 add_to_linker(
242 self.linker(),
243 Self::get_table,
244 Self::get_random,
245 Self::get_clocks,
246 Self::get_cli,
247 Self::get_filesystem,
248 Self::get_spin_sockets,
249 )
250 }
251}
252
253impl<T> InitContextExt for T where T: InitContext<WasiFactor> {}
254
255struct HasIo;
256
257impl HasData for HasIo {
258 type Data<'a> = &'a mut ResourceTable;
259}
260
261impl Factor for WasiFactor {
262 type RuntimeConfig = ();
263 type AppState = ();
264 type InstanceBuilder = InstanceBuilder;
265
266 fn init(&mut self, ctx: &mut impl InitContext<Self>) -> anyhow::Result<()> {
267 use wasmtime_wasi::{p2, p3};
268
269 ctx.link_clocks_bindings(p2::bindings::clocks::wall_clock::add_to_linker::<_, WasiClocks>)?;
270 ctx.link_clocks_bindings(
271 p3::bindings::clocks::system_clock::add_to_linker::<_, WasiClocks>,
272 )?;
273 ctx.link_clocks_bindings(
274 p2::bindings::clocks::monotonic_clock::add_to_linker::<_, WasiClocks>,
275 )?;
276 ctx.link_clocks_bindings(
277 p3::bindings::clocks::monotonic_clock::add_to_linker::<_, WasiClocks>,
278 )?;
279 ctx.link_filesystem_bindings(
280 p2::bindings::filesystem::types::add_to_linker::<_, WasiFilesystem>,
281 )?;
282 ctx.link_filesystem_bindings(
283 p3::bindings::filesystem::types::add_to_linker::<_, WasiFilesystem>,
284 )?;
285 ctx.link_filesystem_bindings(
286 p2::bindings::filesystem::preopens::add_to_linker::<_, WasiFilesystem>,
287 )?;
288 ctx.link_filesystem_bindings(
289 p3::bindings::filesystem::preopens::add_to_linker::<_, WasiFilesystem>,
290 )?;
291 ctx.link_io_bindings(p2::bindings::io::error::add_to_linker::<_, HasIo>)?;
292 ctx.link_io_bindings(p2::bindings::io::poll::add_to_linker::<_, HasIo>)?;
293 ctx.link_io_bindings(p2::bindings::io::streams::add_to_linker::<_, HasIo>)?;
294 ctx.link_random_bindings(p2::bindings::random::random::add_to_linker::<_, WasiRandom>)?;
295 ctx.link_random_bindings(p3::bindings::random::random::add_to_linker::<_, WasiRandom>)?;
296 ctx.link_random_bindings(p2::bindings::random::insecure::add_to_linker::<_, WasiRandom>)?;
297 ctx.link_random_bindings(p3::bindings::random::insecure::add_to_linker::<_, WasiRandom>)?;
298 ctx.link_random_bindings(
299 p2::bindings::random::insecure_seed::add_to_linker::<_, WasiRandom>,
300 )?;
301 ctx.link_random_bindings(
302 p3::bindings::random::insecure_seed::add_to_linker::<_, WasiRandom>,
303 )?;
304 ctx.link_cli_default_bindings(p2::bindings::cli::exit::add_to_linker::<_, WasiCli>)?;
305 ctx.link_cli_default_bindings(p3::bindings::cli::exit::add_to_linker::<_, WasiCli>)?;
306 ctx.link_cli_bindings(p2::bindings::cli::environment::add_to_linker::<_, WasiCli>)?;
307 ctx.link_cli_bindings(p3::bindings::cli::environment::add_to_linker::<_, WasiCli>)?;
308 ctx.link_cli_bindings(p2::bindings::cli::stdin::add_to_linker::<_, WasiCli>)?;
309 ctx.link_cli_bindings(p3::bindings::cli::stdin::add_to_linker::<_, WasiCli>)?;
310 ctx.link_cli_bindings(p2::bindings::cli::stdout::add_to_linker::<_, WasiCli>)?;
311 ctx.link_cli_bindings(p3::bindings::cli::stdout::add_to_linker::<_, WasiCli>)?;
312 ctx.link_cli_bindings(p2::bindings::cli::stderr::add_to_linker::<_, WasiCli>)?;
313 ctx.link_cli_bindings(p3::bindings::cli::stderr::add_to_linker::<_, WasiCli>)?;
314 ctx.link_cli_bindings(p2::bindings::cli::terminal_input::add_to_linker::<_, WasiCli>)?;
315 ctx.link_cli_bindings(p3::bindings::cli::terminal_input::add_to_linker::<_, WasiCli>)?;
316 ctx.link_cli_bindings(p2::bindings::cli::terminal_output::add_to_linker::<_, WasiCli>)?;
317 ctx.link_cli_bindings(p3::bindings::cli::terminal_output::add_to_linker::<_, WasiCli>)?;
318 ctx.link_cli_bindings(p2::bindings::cli::terminal_stdin::add_to_linker::<_, WasiCli>)?;
319 ctx.link_cli_bindings(p3::bindings::cli::terminal_stdin::add_to_linker::<_, WasiCli>)?;
320 ctx.link_cli_bindings(p2::bindings::cli::terminal_stdout::add_to_linker::<_, WasiCli>)?;
321 ctx.link_cli_bindings(p3::bindings::cli::terminal_stdout::add_to_linker::<_, WasiCli>)?;
322 ctx.link_cli_bindings(p2::bindings::cli::terminal_stderr::add_to_linker::<_, WasiCli>)?;
323 ctx.link_cli_bindings(p3::bindings::cli::terminal_stderr::add_to_linker::<_, WasiCli>)?;
324 ctx.link_spin_sockets_bindings(
325 p2::bindings::sockets::tcp::add_to_linker::<_, SpinSockets>,
326 )?;
327 ctx.link_spin_sockets_bindings(
328 p2::bindings::sockets::tcp_create_socket::add_to_linker::<_, SpinSockets>,
329 )?;
330 ctx.link_spin_sockets_bindings(
331 p2::bindings::sockets::udp::add_to_linker::<_, SpinSockets>,
332 )?;
333 ctx.link_spin_sockets_bindings(
334 p2::bindings::sockets::udp_create_socket::add_to_linker::<_, SpinSockets>,
335 )?;
336 ctx.link_sockets_bindings(
337 p2::bindings::sockets::instance_network::add_to_linker::<_, WasiSockets>,
338 )?;
339 ctx.link_sockets_default_bindings(
340 p2::bindings::sockets::network::add_to_linker::<_, WasiSockets>,
341 )?;
342 ctx.link_sockets_bindings(
343 p2::bindings::sockets::ip_name_lookup::add_to_linker::<_, WasiSockets>,
344 )?;
345 ctx.link_sockets_bindings(
346 p3::bindings::sockets::ip_name_lookup::add_to_linker::<_, WasiSockets>,
347 )?;
348 ctx.link_sockets_bindings(p3::bindings::sockets::types::add_to_linker::<_, WasiSockets>)?;
350
351 ctx.link_all_bindings(wasi_2023_10_18::add_to_linker)?;
352 ctx.link_all_bindings(wasi_2023_11_10::add_to_linker)?;
353 Ok(())
354 }
355
356 fn configure_app<T: RuntimeFactors>(
357 &self,
358 _ctx: spin_factors::ConfigureAppContext<T, Self>,
359 ) -> anyhow::Result<Self::AppState> {
360 Ok(())
361 }
362
363 fn prepare<T: RuntimeFactors>(
364 &self,
365 ctx: PrepareContext<T, Self>,
366 ) -> anyhow::Result<InstanceBuilder> {
367 let mut wasi_ctx = WasiCtxBuilder::new();
368
369 let mount_ctx = MountFilesContext { ctx: &mut wasi_ctx };
371 self.files_mounter
372 .mount_files(ctx.app_component(), mount_ctx)?;
373
374 let mut builder = InstanceBuilder {
375 ctx: wasi_ctx,
376 socket_permit_state: None,
377 };
378
379 builder.env(ctx.app_component().environment());
381
382 Ok(builder)
383 }
384}
385
386pub trait FilesMounter: Send + Sync {
387 fn mount_files(
388 &self,
389 app_component: &AppComponent,
390 ctx: MountFilesContext,
391 ) -> anyhow::Result<()>;
392}
393
394pub struct DummyFilesMounter;
395
396impl FilesMounter for DummyFilesMounter {
397 fn mount_files(
398 &self,
399 app_component: &AppComponent,
400 _ctx: MountFilesContext,
401 ) -> anyhow::Result<()> {
402 anyhow::ensure!(
403 app_component.files().next().is_none(),
404 "DummyFilesMounter can't actually mount files"
405 );
406 Ok(())
407 }
408}
409
410pub struct MountFilesContext<'a> {
411 ctx: &'a mut WasiCtxBuilder,
412}
413
414impl MountFilesContext<'_> {
415 pub fn preopened_dir(
416 &mut self,
417 host_path: impl AsRef<Path>,
418 guest_path: impl AsRef<str>,
419 writable: bool,
420 ) -> anyhow::Result<()> {
421 let (dir_perms, file_perms) = if writable {
422 (DirPerms::all(), FilePerms::all())
423 } else {
424 (DirPerms::READ, FilePerms::READ)
425 };
426 self.ctx
427 .preopened_dir(host_path, guest_path, dir_perms, file_perms)?;
428 Ok(())
429 }
430}
431
432pub struct InstanceBuilder {
433 ctx: WasiCtxBuilder,
434 socket_permit_state: Option<Arc<SocketPermitState>>,
435}
436
437impl InstanceBuilder {
438 pub fn stdin(&mut self, stdin: impl StdinStream + 'static) {
440 self.ctx.stdin(stdin);
441 }
442
443 pub fn stdin_pipe(&mut self, r: impl Read + Send + Sync + Unpin + 'static) {
445 self.stdin(PipeReadStream::new(r));
446 }
447
448 pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) {
450 self.ctx.stdout(stdout);
451 }
452
453 pub fn stdout_pipe(&mut self, w: impl Write + Send + Sync + Unpin + 'static) {
455 self.stdout(PipedWriteStream::new(w));
456 }
457
458 pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) {
460 self.ctx.stderr(stderr);
461 }
462
463 pub fn stderr_pipe(&mut self, w: impl Write + Send + Sync + Unpin + 'static) {
465 self.stderr(PipedWriteStream::new(w));
466 }
467
468 pub fn args(&mut self, args: impl IntoIterator<Item = impl AsRef<str>>) {
470 for arg in args {
471 self.ctx.arg(arg);
472 }
473 }
474
475 pub fn env(&mut self, vars: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>)>) {
477 for (k, v) in vars {
478 self.ctx.env(k, v);
479 }
480 }
481
482 pub fn preopened_dir(
485 &mut self,
486 host_path: impl AsRef<Path>,
487 guest_path: impl AsRef<str>,
488 writable: bool,
489 ) -> anyhow::Result<()> {
490 let (dir_perms, file_perms) = if writable {
491 (DirPerms::all(), FilePerms::all())
492 } else {
493 (DirPerms::READ, FilePerms::READ)
494 };
495 self.ctx
496 .preopened_dir(host_path, guest_path, dir_perms, file_perms)?;
497 Ok(())
498 }
499}
500
501impl FactorInstanceBuilder for InstanceBuilder {
502 type InstanceState = InstanceState;
503
504 fn build(self) -> anyhow::Result<Self::InstanceState> {
505 let InstanceBuilder {
506 ctx: mut wasi_ctx,
507 socket_permit_state,
508 } = self;
509 Ok(InstanceState {
510 ctx: wasi_ctx.build(),
511 socket_permit_state,
512 })
513 }
514}
515
516impl InstanceBuilder {
517 pub fn set_socket_permit_state(&mut self, state: Arc<SocketPermitState>) {
519 self.socket_permit_state = Some(state);
520 }
521
522 pub fn outbound_socket_addr_check<F, Fut>(&mut self, check: F)
523 where
524 F: Fn(SocketAddr, SocketAddrUse) -> Fut + Send + Sync + Clone + 'static,
525 Fut: Future<Output = bool> + Send + Sync,
526 {
527 self.ctx.socket_addr_check(move |addr, addr_use| {
528 let check = check.clone();
529 Box::pin(async move {
530 match addr_use {
531 SocketAddrUse::TcpBind => false,
532 SocketAddrUse::TcpConnect
533 | SocketAddrUse::UdpBind
534 | SocketAddrUse::UdpConnect
535 | SocketAddrUse::UdpOutgoingDatagram => check(addr, addr_use).await,
536 }
537 })
538 });
539 }
540}
541
542pub struct InstanceState {
543 ctx: WasiCtx,
544 socket_permit_state: Option<Arc<SocketPermitState>>,
545}