1use crate::error;
2use crate::utils;
3use zwasm_sys as sys;
4
5pub struct WasiConfig {
15 pub(crate) ptr: *mut sys::zwasm_wasi_config_t,
16 argv: Vec<std::ffi::CString>,
17 argv_ptrs: Vec<*const std::os::raw::c_char>,
18 env_keys: Vec<std::ffi::CString>,
19 env_key_lens: Vec<usize>,
20 env_key_ptrs: Vec<*const std::os::raw::c_char>,
21 env_vals: Vec<std::ffi::CString>,
22 env_val_lens: Vec<usize>,
23 env_val_ptrs: Vec<*const std::os::raw::c_char>,
24 preopens: Vec<(std::ffi::CString, std::ffi::CString)>,
25 preopen_fd_guest_paths: Vec<std::ffi::CString>,
26 _not_send_sync: std::marker::PhantomData<std::rc::Rc<()>>,
27}
28
29impl WasiConfig {
30 pub fn new() -> Result<Self, error::ZwasmError> {
34 let ptr = unsafe { sys::zwasm_wasi_config_new() };
35
36 if ptr.is_null() {
37 Err(error::last_error()
38 .unwrap_or_else(|| error::ZwasmError("Unknown error".to_string())))
39 } else {
40 Ok(WasiConfig {
41 ptr,
42 argv: Vec::new(),
43 argv_ptrs: Vec::new(),
44 env_keys: Vec::new(),
45 env_key_lens: Vec::new(),
46 env_key_ptrs: Vec::new(),
47 env_vals: Vec::new(),
48 env_val_lens: Vec::new(),
49 env_val_ptrs: Vec::new(),
50 preopens: Vec::new(),
51 preopen_fd_guest_paths: Vec::new(),
52 _not_send_sync: std::marker::PhantomData,
53 })
54 }
55 }
56
57 pub fn set_argv(&mut self, argv: &[&str]) -> Result<(), error::ZwasmError> {
61 self.argv = argv
62 .iter()
63 .map(|s| {
64 std::ffi::CString::new(*s)
65 .map_err(|_| error::ZwasmError("argument contains NUL byte".into()))
66 })
67 .collect::<Result<Vec<_>, _>>()?;
68
69 self.argv_ptrs = self.argv.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
70 let argc = utils::to_u32_len(self.argv_ptrs.len())?;
71
72 unsafe {
73 sys::zwasm_wasi_config_set_argv(
74 self.ptr,
75 argc,
76 if self.argv_ptrs.is_empty() {
77 std::ptr::null()
78 } else {
79 self.argv_ptrs.as_ptr()
80 },
81 )
82 };
83
84 Ok(())
85 }
86
87 pub fn set_env(&mut self, env: &[(&str, &str)]) -> Result<(), error::ZwasmError> {
91 let c_keys = env
92 .iter()
93 .map(|(key, _)| {
94 std::ffi::CString::new(*key).map_err(|_| {
95 error::ZwasmError("environment variable key contains NUL byte".into())
96 })
97 })
98 .collect::<Result<Vec<_>, _>>()?;
99 let c_key_lens = c_keys
100 .iter()
101 .map(|s| s.as_bytes().len())
102 .collect::<Vec<_>>();
103 let c_key_ptrs = c_keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
104 let c_vals = env
105 .iter()
106 .map(|(_, val)| {
107 std::ffi::CString::new(*val).map_err(|_| {
108 error::ZwasmError("environment variable value contains NUL byte".into())
109 })
110 })
111 .collect::<Result<Vec<_>, _>>()?;
112 let c_val_lens = c_vals
113 .iter()
114 .map(|s| s.as_bytes().len())
115 .collect::<Vec<_>>();
116 let c_val_ptrs = c_vals.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
117 let count = utils::to_u32_len(c_key_ptrs.len())?;
118
119 self.env_keys = c_keys;
120 self.env_key_lens = c_key_lens;
121 self.env_key_ptrs = c_key_ptrs;
122 self.env_vals = c_vals;
123 self.env_val_lens = c_val_lens;
124 self.env_val_ptrs = c_val_ptrs;
125
126 unsafe {
127 sys::zwasm_wasi_config_set_env(
128 self.ptr,
129 count,
130 if self.env_key_ptrs.is_empty() {
131 std::ptr::null()
132 } else {
133 self.env_key_ptrs.as_ptr()
134 },
135 if self.env_key_lens.is_empty() {
136 std::ptr::null()
137 } else {
138 self.env_key_lens.as_ptr()
139 },
140 if self.env_val_ptrs.is_empty() {
141 std::ptr::null()
142 } else {
143 self.env_val_ptrs.as_ptr()
144 },
145 if self.env_val_lens.is_empty() {
146 std::ptr::null()
147 } else {
148 self.env_val_lens.as_ptr()
149 },
150 )
151 };
152
153 Ok(())
154 }
155
156 pub fn preopen_dir(
164 &mut self,
165 host_path: &str,
166 guest_path: &str,
167 ) -> Result<(), error::ZwasmError> {
168 let c_host_path = std::ffi::CString::new(host_path)
169 .map_err(|_| error::ZwasmError("host path contains NUL byte".into()))?;
170 let c_guest_path = std::ffi::CString::new(guest_path)
171 .map_err(|_| error::ZwasmError("guest path contains NUL byte".into()))?;
172
173 self.preopens.push((c_host_path, c_guest_path));
174 let (c_host_path, c_guest_path) = &self.preopens[self.preopens.len() - 1];
175 let c_host_path_len = c_host_path.as_bytes().len();
176 let c_guest_path_len = c_guest_path.as_bytes().len();
177
178 unsafe {
179 sys::zwasm_wasi_config_preopen_dir(
180 self.ptr,
181 c_host_path.as_ptr(),
182 c_host_path_len,
183 c_guest_path.as_ptr(),
184 c_guest_path_len,
185 )
186 };
187
188 Ok(())
189 }
190
191 pub fn preopen_fd(
200 &mut self,
201 host_fd: isize,
202 guest_path: &str,
203 kind: u8,
204 ownership: u8,
205 ) -> Result<(), error::ZwasmError> {
206 let c_guest_path = std::ffi::CString::new(guest_path)
207 .map_err(|_| error::ZwasmError("guest path contains NUL byte".into()))?;
208
209 self.preopen_fd_guest_paths.push(c_guest_path);
211 let c_guest_path = self.preopen_fd_guest_paths.last().ok_or_else(|| {
212 error::ZwasmError("internal error: failed to retain preopen fd guest path".into())
213 })?;
214 let c_guest_path_len = c_guest_path.as_bytes().len();
215
216 unsafe {
217 sys::zwasm_wasi_config_preopen_fd(
218 self.ptr,
219 host_fd,
220 c_guest_path.as_ptr(),
221 c_guest_path_len,
222 kind,
223 ownership,
224 )
225 };
226
227 Ok(())
228 }
229
230 pub fn set_stdio_fd(
238 &mut self,
239 wasi_fd: u32,
240 host_fd: isize,
241 ownership: u8,
242 ) -> Result<(), error::ZwasmError> {
243 unsafe {
244 sys::zwasm_wasi_config_set_stdio_fd(self.ptr, wasi_fd, host_fd, ownership);
245 }
246 Ok(())
247 }
248}
249
250impl Drop for WasiConfig {
251 fn drop(&mut self) {
252 unsafe {
253 sys::zwasm_wasi_config_delete(self.ptr);
254 }
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use std::os::fd::AsRawFd;
261
262 use super::*;
263
264 #[test]
265 fn test_wasi_fd_api() {
266 let mut wc = WasiConfig::new().expect("Failed to create WasiConfig");
267
268 let (stdout_read, stdout_write) =
270 nix::unistd::pipe().expect("Failed to create pipe for stdout");
271
272 wc.set_stdio_fd(1, stdout_write.as_raw_fd() as isize, 0 )
274 .expect("Failed to set stdio fd for stdout");
275
276 wc.set_stdio_fd(2, stdout_write.as_raw_fd() as isize, 0 )
278 .expect("Failed to set stdio fd for stderr");
279
280 wc.set_stdio_fd(5, stdout_read.as_raw_fd() as isize, 0)
282 .expect("Failed to set stdio fd for invalid index");
283
284 let dir_fd = nix::fcntl::open(
286 ".",
287 nix::fcntl::OFlag::O_RDONLY,
288 nix::sys::stat::Mode::empty(),
289 )
290 .expect("Failed to open current directory for preopen fd");
291 wc.preopen_fd(
292 dir_fd.as_raw_fd() as isize,
293 "/sandbox",
294 1, 0, )
297 .expect("Failed to add preopen fd");
298
299 drop(wc);
300
301 let written = nix::unistd::write(&stdout_write, b"ok")
302 .expect("Failed to write to borrowed stdout pipe");
303 assert_eq!(written, 2, "borrowed stdout pipe still writable");
304
305 let _stat = nix::sys::stat::fstat(&dir_fd)
306 .expect("borrowed dir fd still valid after WasiConfig drop");
307 }
308}