1 //! The WASI embedding API definitions for Wasmtime.
2 use crate::host_ref::HostRef;
3 use crate::{wasm_extern_t, wasm_importtype_t, wasm_store_t, wasm_trap_t, ExternHost};
4 use anyhow::Result;
5 use std::collections::HashMap;
6 use std::ffi::CStr;
7 use std::fs::File;
8 use std::os::raw::{c_char, c_int};
9 use std::path::{Path, PathBuf};
10 use std::slice;
11 use std::str;
12 use wasi_common::{
13 old::snapshot_0::WasiCtxBuilder as WasiSnapshot0CtxBuilder, preopen_dir,
14 WasiCtxBuilder as WasiPreview1CtxBuilder,
15 };
16 use wasmtime::{Linker, Store, Trap};
17 use wasmtime_wasi::{old::snapshot_0::Wasi as WasiSnapshot0, Wasi as WasiPreview1};
18
cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path>19 unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
20 CStr::from_ptr(path).to_str().map(Path::new).ok()
21 }
22
open_file(path: *const c_char) -> Option<File>23 unsafe fn open_file(path: *const c_char) -> Option<File> {
24 File::open(cstr_to_path(path)?).ok()
25 }
26
create_file(path: *const c_char) -> Option<File>27 unsafe fn create_file(path: *const c_char) -> Option<File> {
28 File::create(cstr_to_path(path)?).ok()
29 }
30
31 pub enum WasiModule {
32 Snapshot0(WasiSnapshot0),
33 Preview1(WasiPreview1),
34 }
35
36 impl WasiModule {}
37
38 #[repr(C)]
39 #[derive(Default)]
40 pub struct wasi_config_t {
41 args: Vec<Vec<u8>>,
42 env: Vec<(Vec<u8>, Vec<u8>)>,
43 stdin: Option<File>,
44 stdout: Option<File>,
45 stderr: Option<File>,
46 preopens: Vec<(File, PathBuf)>,
47 inherit_args: bool,
48 inherit_env: bool,
49 inherit_stdin: bool,
50 inherit_stdout: bool,
51 inherit_stderr: bool,
52 }
53
54 #[no_mangle]
wasi_config_new() -> Box<wasi_config_t>55 pub extern "C" fn wasi_config_new() -> Box<wasi_config_t> {
56 Box::new(wasi_config_t::default())
57 }
58
59 #[no_mangle]
wasi_config_delete(_config: Box<wasi_config_t>)60 pub extern "C" fn wasi_config_delete(_config: Box<wasi_config_t>) {}
61
62 #[no_mangle]
wasi_config_set_argv( config: &mut wasi_config_t, argc: c_int, argv: *const *const c_char, )63 pub unsafe extern "C" fn wasi_config_set_argv(
64 config: &mut wasi_config_t,
65 argc: c_int,
66 argv: *const *const c_char,
67 ) {
68 config.args = slice::from_raw_parts(argv, argc as usize)
69 .iter()
70 .map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
71 .collect();
72 config.inherit_args = false;
73 }
74
75 #[no_mangle]
wasi_config_inherit_argv(config: &mut wasi_config_t)76 pub extern "C" fn wasi_config_inherit_argv(config: &mut wasi_config_t) {
77 config.args.clear();
78 config.inherit_args = true;
79 }
80
81 #[no_mangle]
wasi_config_set_env( config: &mut wasi_config_t, envc: c_int, names: *const *const c_char, values: *const *const c_char, )82 pub unsafe extern "C" fn wasi_config_set_env(
83 config: &mut wasi_config_t,
84 envc: c_int,
85 names: *const *const c_char,
86 values: *const *const c_char,
87 ) {
88 let names = slice::from_raw_parts(names, envc as usize);
89 let values = slice::from_raw_parts(values, envc as usize);
90
91 config.env = names
92 .iter()
93 .map(|p| CStr::from_ptr(*p).to_bytes().to_owned())
94 .zip(
95 values
96 .iter()
97 .map(|p| CStr::from_ptr(*p).to_bytes().to_owned()),
98 )
99 .collect();
100 config.inherit_env = false;
101 }
102
103 #[no_mangle]
wasi_config_inherit_env(config: &mut wasi_config_t)104 pub extern "C" fn wasi_config_inherit_env(config: &mut wasi_config_t) {
105 config.env.clear();
106 config.inherit_env = true;
107 }
108
109 #[no_mangle]
wasi_config_set_stdin_file( config: &mut wasi_config_t, path: *const c_char, ) -> bool110 pub unsafe extern "C" fn wasi_config_set_stdin_file(
111 config: &mut wasi_config_t,
112 path: *const c_char,
113 ) -> bool {
114 let file = match open_file(path) {
115 Some(f) => f,
116 None => return false,
117 };
118
119 config.stdin = Some(file);
120 config.inherit_stdin = false;
121
122 true
123 }
124
125 #[no_mangle]
wasi_config_inherit_stdin(config: &mut wasi_config_t)126 pub extern "C" fn wasi_config_inherit_stdin(config: &mut wasi_config_t) {
127 config.stdin = None;
128 config.inherit_stdin = true;
129 }
130
131 #[no_mangle]
wasi_config_set_stdout_file( config: &mut wasi_config_t, path: *const c_char, ) -> bool132 pub unsafe extern "C" fn wasi_config_set_stdout_file(
133 config: &mut wasi_config_t,
134 path: *const c_char,
135 ) -> bool {
136 let file = match create_file(path) {
137 Some(f) => f,
138 None => return false,
139 };
140
141 config.stdout = Some(file);
142 config.inherit_stdout = false;
143
144 true
145 }
146
147 #[no_mangle]
wasi_config_inherit_stdout(config: &mut wasi_config_t)148 pub extern "C" fn wasi_config_inherit_stdout(config: &mut wasi_config_t) {
149 config.stdout = None;
150 config.inherit_stdout = true;
151 }
152
153 #[no_mangle]
wasi_config_set_stderr_file( config: &mut wasi_config_t, path: *const c_char, ) -> bool154 pub unsafe extern "C" fn wasi_config_set_stderr_file(
155 config: &mut wasi_config_t,
156 path: *const c_char,
157 ) -> bool {
158 let file = match create_file(path) {
159 Some(f) => f,
160 None => return false,
161 };
162
163 (*config).stderr = Some(file);
164 (*config).inherit_stderr = false;
165
166 true
167 }
168
169 #[no_mangle]
wasi_config_inherit_stderr(config: &mut wasi_config_t)170 pub extern "C" fn wasi_config_inherit_stderr(config: &mut wasi_config_t) {
171 config.stderr = None;
172 config.inherit_stderr = true;
173 }
174
175 #[no_mangle]
wasi_config_preopen_dir( config: &mut wasi_config_t, path: *const c_char, guest_path: *const c_char, ) -> bool176 pub unsafe extern "C" fn wasi_config_preopen_dir(
177 config: &mut wasi_config_t,
178 path: *const c_char,
179 guest_path: *const c_char,
180 ) -> bool {
181 let guest_path = match cstr_to_path(guest_path) {
182 Some(p) => p,
183 None => return false,
184 };
185
186 let dir = match cstr_to_path(path) {
187 Some(p) => match preopen_dir(p) {
188 Ok(d) => d,
189 Err(_) => return false,
190 },
191 None => return false,
192 };
193
194 (*config).preopens.push((dir, guest_path.to_owned()));
195
196 true
197 }
198
199 enum WasiInstance {
200 Preview1(WasiPreview1),
201 Snapshot0(WasiSnapshot0),
202 }
203
204 macro_rules! config_to_builder {
205 ($builder:ident, $config:ident) => {{
206 let mut builder = $builder::new();
207
208 if $config.inherit_args {
209 builder.inherit_args();
210 } else if !$config.args.is_empty() {
211 builder.args($config.args);
212 }
213
214 if $config.inherit_env {
215 builder.inherit_env();
216 } else if !$config.env.is_empty() {
217 builder.envs($config.env);
218 }
219
220 if $config.inherit_stdin {
221 builder.inherit_stdin();
222 } else if let Some(file) = $config.stdin {
223 builder.stdin(file);
224 }
225
226 if $config.inherit_stdout {
227 builder.inherit_stdout();
228 } else if let Some(file) = $config.stdout {
229 builder.stdout(file);
230 }
231
232 if $config.inherit_stderr {
233 builder.inherit_stderr();
234 } else if let Some(file) = $config.stderr {
235 builder.stderr(file);
236 }
237
238 for preopen in $config.preopens {
239 builder.preopened_dir(preopen.0, preopen.1);
240 }
241
242 builder
243 }};
244 }
245
create_snapshot0_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String>246 fn create_snapshot0_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String> {
247 Ok(WasiInstance::Snapshot0(WasiSnapshot0::new(
248 store,
249 config_to_builder!(WasiSnapshot0CtxBuilder, config)
250 .build()
251 .map_err(|e| e.to_string())?,
252 )))
253 }
254
create_preview1_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String>255 fn create_preview1_instance(store: &Store, config: wasi_config_t) -> Result<WasiInstance, String> {
256 Ok(WasiInstance::Preview1(WasiPreview1::new(
257 store,
258 config_to_builder!(WasiPreview1CtxBuilder, config)
259 .build()
260 .map_err(|e| e.to_string())?,
261 )))
262 }
263
264 #[repr(C)]
265 pub struct wasi_instance_t {
266 wasi: WasiInstance,
267 export_cache: HashMap<String, Box<wasm_extern_t>>,
268 }
269
270 impl wasi_instance_t {
add_to_linker(&self, linker: &mut Linker) -> Result<()>271 pub fn add_to_linker(&self, linker: &mut Linker) -> Result<()> {
272 match &self.wasi {
273 WasiInstance::Snapshot0(w) => w.add_to_linker(linker),
274 WasiInstance::Preview1(w) => w.add_to_linker(linker),
275 }
276 }
277 }
278
279 #[no_mangle]
wasi_instance_new( store: &wasm_store_t, name: *const c_char, config: Box<wasi_config_t>, trap: &mut *mut wasm_trap_t, ) -> Option<Box<wasi_instance_t>>280 pub unsafe extern "C" fn wasi_instance_new(
281 store: &wasm_store_t,
282 name: *const c_char,
283 config: Box<wasi_config_t>,
284 trap: &mut *mut wasm_trap_t,
285 ) -> Option<Box<wasi_instance_t>> {
286 let store = &store.store;
287
288 let result = match CStr::from_ptr(name).to_str().unwrap_or("") {
289 "wasi_snapshot_preview1" => create_preview1_instance(store, *config),
290 "wasi_unstable" => create_snapshot0_instance(store, *config),
291 _ => Err("unsupported WASI version".into()),
292 };
293
294 match result {
295 Ok(wasi) => Some(Box::new(wasi_instance_t {
296 wasi,
297 export_cache: HashMap::new(),
298 })),
299 Err(e) => {
300 *trap = Box::into_raw(Box::new(wasm_trap_t {
301 trap: HostRef::new(store, Trap::new(e)),
302 }));
303
304 None
305 }
306 }
307 }
308
309 #[no_mangle]
wasi_instance_delete(_instance: Box<wasi_instance_t>)310 pub extern "C" fn wasi_instance_delete(_instance: Box<wasi_instance_t>) {}
311
312 #[no_mangle]
wasi_instance_bind_import<'a>( instance: &'a mut wasi_instance_t, import: &wasm_importtype_t, ) -> Option<&'a wasm_extern_t>313 pub extern "C" fn wasi_instance_bind_import<'a>(
314 instance: &'a mut wasi_instance_t,
315 import: &wasm_importtype_t,
316 ) -> Option<&'a wasm_extern_t> {
317 let module = &import.module;
318 let name = str::from_utf8(import.name.as_bytes()).ok()?;
319
320 let export = match &instance.wasi {
321 WasiInstance::Preview1(wasi) => {
322 if module != "wasi_snapshot_preview1" {
323 return None;
324 }
325 wasi.get_export(&name)?
326 }
327 WasiInstance::Snapshot0(wasi) => {
328 if module != "wasi_unstable" {
329 return None;
330 }
331
332 wasi.get_export(&name)?
333 }
334 };
335
336 if &export.ty() != import.ty.func()? {
337 return None;
338 }
339 let store = export.store();
340
341 let entry = instance
342 .export_cache
343 .entry(name.to_string())
344 .or_insert_with(|| {
345 Box::new(wasm_extern_t {
346 which: ExternHost::Func(HostRef::new(store, export.clone())),
347 })
348 });
349 Some(entry)
350 }
351