xref: /wasmtime-44.0.1/crates/c-api/src/wasi.rs (revision dc029724)
1 //! The WASI embedding API definitions for Wasmtime.
2 
3 use crate::wasm_byte_vec_t;
4 use bytes::Bytes;
5 use std::ffi::{CStr, c_char, c_void};
6 use std::fs::File;
7 use std::path::Path;
8 use std::pin::Pin;
9 use std::slice;
10 use std::task::{Context, Poll};
11 use tokio::io::{self, AsyncWrite};
12 use wasmtime::Result;
13 use wasmtime_wasi::WasiCtxBuilder;
14 use wasmtime_wasi::p1::WasiP1Ctx;
15 use wasmtime_wasi_io::streams::StreamError;
16 
cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path>17 unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> {
18     CStr::from_ptr(path).to_str().map(Path::new).ok()
19 }
20 
cstr_to_str<'a>(s: *const c_char) -> Option<&'a str>21 unsafe fn cstr_to_str<'a>(s: *const c_char) -> Option<&'a str> {
22     CStr::from_ptr(s).to_str().ok()
23 }
24 
open_file(path: *const c_char) -> Option<File>25 unsafe fn open_file(path: *const c_char) -> Option<File> {
26     File::open(cstr_to_path(path)?).ok()
27 }
28 
create_file(path: *const c_char) -> Option<File>29 unsafe fn create_file(path: *const c_char) -> Option<File> {
30     File::create(cstr_to_path(path)?).ok()
31 }
32 
33 #[repr(C)]
34 pub struct wasi_config_t {
35     builder: WasiCtxBuilder,
36 }
37 
38 wasmtime_c_api_macros::declare_own!(wasi_config_t);
39 
40 impl wasi_config_t {
into_wasi_ctx(mut self) -> Result<WasiP1Ctx>41     pub fn into_wasi_ctx(mut self) -> Result<WasiP1Ctx> {
42         Ok(self.builder.build_p1())
43     }
44 }
45 
46 #[unsafe(no_mangle)]
wasi_config_new() -> Box<wasi_config_t>47 pub extern "C" fn wasi_config_new() -> Box<wasi_config_t> {
48     Box::new(wasi_config_t {
49         builder: WasiCtxBuilder::new(),
50     })
51 }
52 
53 #[unsafe(no_mangle)]
wasi_config_set_argv( config: &mut wasi_config_t, argc: usize, argv: *const *const c_char, ) -> bool54 pub unsafe extern "C" fn wasi_config_set_argv(
55     config: &mut wasi_config_t,
56     argc: usize,
57     argv: *const *const c_char,
58 ) -> bool {
59     for arg in slice::from_raw_parts(argv, argc) {
60         let arg = match CStr::from_ptr(*arg).to_str() {
61             Ok(s) => s,
62             Err(_) => return false,
63         };
64         config.builder.arg(arg);
65     }
66     true
67 }
68 
69 #[unsafe(no_mangle)]
wasi_config_inherit_argv(config: &mut wasi_config_t)70 pub extern "C" fn wasi_config_inherit_argv(config: &mut wasi_config_t) {
71     config.builder.inherit_args();
72 }
73 
74 #[unsafe(no_mangle)]
wasi_config_set_env( config: &mut wasi_config_t, envc: usize, names: *const *const c_char, values: *const *const c_char, ) -> bool75 pub unsafe extern "C" fn wasi_config_set_env(
76     config: &mut wasi_config_t,
77     envc: usize,
78     names: *const *const c_char,
79     values: *const *const c_char,
80 ) -> bool {
81     let names = slice::from_raw_parts(names, envc);
82     let values = slice::from_raw_parts(values, envc);
83 
84     for (k, v) in names.iter().zip(values) {
85         let k = match cstr_to_str(*k) {
86             Some(s) => s,
87             None => return false,
88         };
89         let v = match cstr_to_str(*v) {
90             Some(s) => s,
91             None => return false,
92         };
93         config.builder.env(k, v);
94     }
95     true
96 }
97 
98 #[unsafe(no_mangle)]
wasi_config_inherit_env(config: &mut wasi_config_t)99 pub extern "C" fn wasi_config_inherit_env(config: &mut wasi_config_t) {
100     config.builder.inherit_env();
101 }
102 
103 #[unsafe(no_mangle)]
wasi_config_set_stdin_file( config: &mut wasi_config_t, path: *const c_char, ) -> bool104 pub unsafe extern "C" fn wasi_config_set_stdin_file(
105     config: &mut wasi_config_t,
106     path: *const c_char,
107 ) -> bool {
108     let file = match open_file(path) {
109         Some(f) => f,
110         None => return false,
111     };
112 
113     let file = tokio::fs::File::from_std(file);
114     let stdin_stream = wasmtime_wasi::cli::AsyncStdinStream::new(file);
115     config.builder.stdin(stdin_stream);
116 
117     true
118 }
119 
120 #[unsafe(no_mangle)]
wasi_config_set_stdin_bytes( config: &mut wasi_config_t, binary: &mut wasm_byte_vec_t, )121 pub unsafe extern "C" fn wasi_config_set_stdin_bytes(
122     config: &mut wasi_config_t,
123     binary: &mut wasm_byte_vec_t,
124 ) {
125     let binary = binary.take();
126     let binary = wasmtime_wasi::p2::pipe::MemoryInputPipe::new(binary);
127     config.builder.stdin(binary);
128 }
129 
130 #[unsafe(no_mangle)]
wasi_config_inherit_stdin(config: &mut wasi_config_t)131 pub extern "C" fn wasi_config_inherit_stdin(config: &mut wasi_config_t) {
132     config.builder.inherit_stdin();
133 }
134 
135 #[unsafe(no_mangle)]
wasi_config_set_stdout_file( config: &mut wasi_config_t, path: *const c_char, ) -> bool136 pub unsafe extern "C" fn wasi_config_set_stdout_file(
137     config: &mut wasi_config_t,
138     path: *const c_char,
139 ) -> bool {
140     let file = match create_file(path) {
141         Some(f) => f,
142         None => return false,
143     };
144 
145     config
146         .builder
147         .stdout(wasmtime_wasi::cli::OutputFile::new(file));
148 
149     true
150 }
151 
152 #[unsafe(no_mangle)]
wasi_config_inherit_stdout(config: &mut wasi_config_t)153 pub extern "C" fn wasi_config_inherit_stdout(config: &mut wasi_config_t) {
154     config.builder.inherit_stdout();
155 }
156 
157 struct CustomOutputStreamInner {
158     foreign_data: crate::ForeignData,
159     callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
160 }
161 
162 impl CustomOutputStreamInner {
raw_write(&self, buf: &[u8]) -> io::Result<usize>163     pub fn raw_write(&self, buf: &[u8]) -> io::Result<usize> {
164         let wrote = (self.callback)(self.foreign_data.data, buf.as_ptr(), buf.len());
165 
166         if wrote >= 0 {
167             Ok(wrote as _)
168         } else {
169             Err(io::Error::from_raw_os_error(wrote.abs() as _))
170         }
171     }
172 }
173 
174 #[derive(Clone)]
175 pub struct CustomOutputStream {
176     inner: std::sync::Arc<CustomOutputStreamInner>,
177 }
178 
179 impl CustomOutputStream {
new( foreign_data: crate::ForeignData, callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize, ) -> Self180     pub fn new(
181         foreign_data: crate::ForeignData,
182         callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
183     ) -> Self {
184         Self {
185             inner: std::sync::Arc::new(CustomOutputStreamInner {
186                 foreign_data,
187                 callback,
188             }),
189         }
190     }
191 }
192 
193 #[async_trait::async_trait]
194 impl wasmtime_wasi::p2::Pollable for CustomOutputStream {
ready(&mut self)195     async fn ready(&mut self) {}
196 }
197 
198 #[async_trait::async_trait]
199 impl wasmtime_wasi::p2::OutputStream for CustomOutputStream {
write(&mut self, bytes: Bytes) -> Result<(), StreamError>200     fn write(&mut self, bytes: Bytes) -> Result<(), StreamError> {
201         let wrote = self
202             .inner
203             .raw_write(&bytes)
204             .map_err(|e| StreamError::LastOperationFailed(e.into()))?;
205 
206         if wrote != bytes.len() {
207             return Err(StreamError::LastOperationFailed(wasmtime::format_err!(
208                 "Partial writes in wasip2 implementation are not allowed"
209             )));
210         }
211 
212         Ok(())
213     }
flush(&mut self) -> Result<(), StreamError>214     fn flush(&mut self) -> Result<(), StreamError> {
215         Ok(())
216     }
check_write(&mut self) -> Result<usize, StreamError>217     fn check_write(&mut self) -> Result<usize, StreamError> {
218         Ok(usize::MAX)
219     }
220 }
221 
222 impl AsyncWrite for CustomOutputStream {
poll_write( self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8], ) -> Poll<io::Result<usize>>223     fn poll_write(
224         self: Pin<&mut Self>,
225         _cx: &mut Context<'_>,
226         buf: &[u8],
227     ) -> Poll<io::Result<usize>> {
228         Poll::Ready(self.inner.raw_write(buf))
229     }
poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>>230     fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
231         Poll::Ready(Ok(()))
232     }
poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>>233     fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
234         Poll::Ready(Ok(()))
235     }
236 }
237 
238 impl wasmtime_wasi::cli::IsTerminal for CustomOutputStream {
is_terminal(&self) -> bool239     fn is_terminal(&self) -> bool {
240         false
241     }
242 }
243 
244 impl wasmtime_wasi::cli::StdoutStream for CustomOutputStream {
async_stream(&self) -> Box<dyn AsyncWrite + Send + Sync>245     fn async_stream(&self) -> Box<dyn AsyncWrite + Send + Sync> {
246         Box::new(self.clone())
247     }
248 }
249 
250 #[unsafe(no_mangle)]
wasi_config_set_stdout_custom( config: &mut wasi_config_t, callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize, data: *mut c_void, finalizer: Option<extern "C" fn(*mut c_void)>, )251 pub extern "C" fn wasi_config_set_stdout_custom(
252     config: &mut wasi_config_t,
253     callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
254     data: *mut c_void,
255     finalizer: Option<extern "C" fn(*mut c_void)>,
256 ) {
257     config.builder.stdout(CustomOutputStream::new(
258         crate::ForeignData { data, finalizer },
259         callback,
260     ));
261 }
262 
263 #[unsafe(no_mangle)]
wasi_config_set_stderr_file( config: &mut wasi_config_t, path: *const c_char, ) -> bool264 pub unsafe extern "C" fn wasi_config_set_stderr_file(
265     config: &mut wasi_config_t,
266     path: *const c_char,
267 ) -> bool {
268     let file = match create_file(path) {
269         Some(f) => f,
270         None => return false,
271     };
272 
273     config
274         .builder
275         .stderr(wasmtime_wasi::cli::OutputFile::new(file));
276 
277     true
278 }
279 
280 #[unsafe(no_mangle)]
wasi_config_inherit_stderr(config: &mut wasi_config_t)281 pub extern "C" fn wasi_config_inherit_stderr(config: &mut wasi_config_t) {
282     config.builder.inherit_stderr();
283 }
284 
285 #[unsafe(no_mangle)]
wasi_config_set_stderr_custom( config: &mut wasi_config_t, callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize, data: *mut c_void, finalizer: Option<extern "C" fn(*mut c_void)>, )286 pub extern "C" fn wasi_config_set_stderr_custom(
287     config: &mut wasi_config_t,
288     callback: extern "C" fn(*mut c_void, *const u8, usize) -> isize,
289     data: *mut c_void,
290     finalizer: Option<extern "C" fn(*mut c_void)>,
291 ) {
292     config.builder.stderr(CustomOutputStream::new(
293         crate::ForeignData { data, finalizer },
294         callback,
295     ));
296 }
297 
298 #[unsafe(no_mangle)]
wasi_config_preopen_dir( config: &mut wasi_config_t, path: *const c_char, guest_path: *const c_char, dir_perms: usize, file_perms: usize, ) -> bool299 pub unsafe extern "C" fn wasi_config_preopen_dir(
300     config: &mut wasi_config_t,
301     path: *const c_char,
302     guest_path: *const c_char,
303     dir_perms: usize,
304     file_perms: usize,
305 ) -> bool {
306     let guest_path = match cstr_to_str(guest_path) {
307         Some(p) => p,
308         None => return false,
309     };
310 
311     let host_path = match cstr_to_path(path) {
312         Some(p) => p,
313         None => return false,
314     };
315 
316     let dir_perms = match wasmtime_wasi::DirPerms::from_bits(dir_perms) {
317         Some(p) => p,
318         None => return false,
319     };
320 
321     let file_perms = match wasmtime_wasi::FilePerms::from_bits(file_perms) {
322         Some(p) => p,
323         None => return false,
324     };
325 
326     config
327         .builder
328         .preopened_dir(host_path, guest_path, dir_perms, file_perms)
329         .is_ok()
330 }
331