wasmer_wasix/os/command/builtins/
cmd_wasmer.rs1use std::{any::Any, path::PathBuf, sync::Arc};
2
3use crate::{
4 SpawnError,
5 bin_factory::spawn_exec_wasm,
6 os::task::{OwnedTaskStatus, TaskJoinHandle},
7 runtime::module_cache::HashedModuleData,
8};
9use shared_buffer::OwnedBuffer;
10use virtual_fs::{AsyncReadExt, AsyncWriteExt, FileSystem};
11use virtual_mio::block_on;
12use wasmer::FunctionEnvMut;
13use wasmer_package::utils::from_bytes;
14use wasmer_wasix_types::wasi::Errno;
15
16use crate::{
17 Runtime, WasiEnv,
18 bin_factory::{BinaryPackage, spawn_exec},
19 syscalls::stderr_write,
20};
21
22const HELP: &str = r#"USAGE:
23 wasmer <SUBCOMMAND>
24
25OPTIONS:
26 -h, --help Print help information
27 -V, --version Print version information
28
29SUBCOMMANDS:
30 run Run a WebAssembly file. Formats accepted: wasm, wat
31"#;
32
33const HELP_RUN: &str = r#"USAGE:
34 wasmer run <FILE> [ARGS]...
35
36ARGS:
37 <FILE> File to run
38 <ARGS>... Application arguments
39"#;
40
41use crate::os::command::VirtualCommand;
42
43#[derive(Debug, Clone)]
44pub struct CmdWasmer {
45 runtime: Arc<dyn Runtime + Send + Sync + 'static>,
46}
47
48impl CmdWasmer {
49 const NAME: &'static str = "wasmer";
50
51 pub fn new(runtime: Arc<dyn Runtime + Send + Sync + 'static>) -> Self {
52 Self { runtime }
53 }
54
55 #[allow(clippy::await_holding_lock)]
56 async fn write_stdout(env: &WasiEnv, buf: &[u8]) -> Result<(), Errno> {
57 let fd = env.state.fs.get_fd(1)?;
58 let handle = {
59 let mut guard = fd.inode.write();
60 match &mut *guard {
61 crate::fs::Kind::File {
62 handle: Some(handle),
63 ..
64 } => handle.clone(),
65 crate::fs::Kind::PipeTx { tx } => {
66 return std::io::Write::write_all(tx, buf).map_err(crate::utils::map_io_err);
67 }
68 crate::fs::Kind::DuplexPipe { pipe } => {
69 return std::io::Write::write_all(pipe, buf).map_err(crate::utils::map_io_err);
70 }
71 crate::fs::Kind::Buffer { buffer } => {
72 buffer.extend_from_slice(buf);
73 return Ok(());
74 }
75 _ => return Err(Errno::Badf),
76 }
77 };
78
79 let mut file = crate::utils::write_owned(&handle).unwrap();
80 file.write_all(buf)
81 .await
82 .map_err(crate::utils::map_io_err)?;
83 file.flush().await.map_err(crate::utils::map_io_err)
84 }
85}
86
87#[derive(Debug, Clone)]
88enum Executable {
89 Wasm(OwnedBuffer),
90 BinaryPackage(Box<BinaryPackage>),
91}
92
93impl CmdWasmer {
94 async fn run(
95 &self,
96 parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
97 name: &str,
98 config: &mut Option<WasiEnv>,
99 what: Option<String>,
100 mut args: Vec<String>,
101 ) -> Result<TaskJoinHandle, SpawnError> {
102 if args.first().map(|a| a.as_str()) == Some("--") {
104 args = args.into_iter().skip(1).collect();
105 }
106
107 if let Some(what) = what {
108 let mut env = config.take().ok_or(SpawnError::UnknownError)?;
109
110 let mut state = env.state.fork();
112 args.insert(0, what.clone());
113 state.args = std::sync::Mutex::new(args);
114 env.state = Arc::new(state);
115
116 let file_path = if what.starts_with('/') {
117 PathBuf::from(&what)
118 } else {
119 let cwd = env.state.fs.current_dir.lock().unwrap().clone();
121
122 PathBuf::from(cwd).join(&what)
123 };
124
125 let fs = env.fs_root();
126 let f = fs.new_open_options().read(true).open(&file_path);
127 let executable = if let Ok(mut file) = f {
128 let mut data = Vec::with_capacity(file.size() as usize);
129 file.read_to_end(&mut data).await.unwrap();
130
131 let bytes: bytes::Bytes = data.into();
132
133 if let Ok(container) = from_bytes(bytes.clone()) {
134 let pkg = BinaryPackage::from_webc(&container, &*self.runtime)
135 .await
136 .unwrap();
137
138 Executable::BinaryPackage(Box::new(pkg))
139 } else {
140 Executable::Wasm(OwnedBuffer::from_bytes(bytes))
141 }
142 } else if let Ok(pkg) = self.get_package(&what).await {
143 Executable::BinaryPackage(Box::new(pkg))
144 } else {
145 let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await;
146 let handle =
147 OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
148 return Ok(handle);
149 };
150
151 match executable {
152 Executable::BinaryPackage(binary) => {
153 let cmd_name: &str =
155 binary
156 .infer_entrypoint()
157 .map_err(|_| SpawnError::MissingEntrypoint {
158 package_id: binary.id.clone(),
159 })?;
160
161 {
162 let cmd =
163 binary
164 .get_command(cmd_name)
165 .ok_or_else(|| SpawnError::NotFound {
166 message: format!(
167 "{cmd_name} command in package: {}",
168 binary.id
169 ),
170 })?;
171 env.prepare_spawn(cmd);
172 }
173
174 env.use_package_async(&binary).await.unwrap();
175
176 spawn_exec(*binary, name, env, &self.runtime).await
178 }
179 Executable::Wasm(bytes) => {
180 let data = HashedModuleData::new(bytes);
181 spawn_exec_wasm(data, name, env, &self.runtime).await
182 }
183 }
184 } else {
185 let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await;
186 let handle = OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
187 Ok(handle)
188 }
189 }
190
191 pub async fn get_package(&self, name: &str) -> Result<BinaryPackage, anyhow::Error> {
192 let (tx, rx) = tokio::sync::oneshot::channel();
194 let specifier = name.parse()?;
195 let rt = self.runtime.clone();
196 self.runtime.task_manager().task_shared(Box::new(|| {
197 Box::pin(async move {
198 let res = BinaryPackage::from_registry(&specifier, rt.as_ref()).await;
199 tx.send(res)
200 .expect("could not send response to output channel");
201 })
202 }))?;
203 rx.await
204 .map_err(|_| anyhow::anyhow!("package retrieval response channel died"))?
205 }
206}
207
208impl VirtualCommand for CmdWasmer {
209 fn name(&self) -> &str {
210 Self::NAME
211 }
212
213 fn as_any(&self) -> &dyn Any {
214 self
215 }
216
217 fn exec(
218 &self,
219 parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
220 name: &str,
221 env: &mut Option<WasiEnv>,
222 ) -> Result<TaskJoinHandle, SpawnError> {
223 let env_inner = env.as_ref().ok_or(SpawnError::UnknownError)?;
225 let args = env_inner.state.args.lock().unwrap().clone();
226 let mut args = args.iter().map(|s| s.as_str());
227 let _alias = args.next();
228 let cmd = args.next();
229
230 let fut = async {
232 match cmd {
233 Some("run") => {
234 let what = args.next().map(|a| a.to_string());
235 let args = args.map(|a| a.to_string()).collect();
236 self.run(parent_ctx, name, env, what, args).await
237 }
238 Some("--help") | None => {
239 unsafe { stderr_write(parent_ctx, HELP.as_bytes()) }
240 .await
241 .ok();
242 let handle =
243 OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
244 Ok(handle)
245 }
246 Some("--version" | "-V") => {
247 let version = format!("wasmer {}\n", wasmer_types::VERSION);
248 if let Some(env) = env.as_ref() {
249 Self::write_stdout(env, version.as_bytes()).await.ok();
250 }
251 let handle =
252 OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
253 Ok(handle)
254 }
255 Some(what) => {
256 let what = Some(what.to_string());
257 let args = args.map(|a| a.to_string()).collect();
258 self.run(parent_ctx, name, env, what, args).await
259 }
260 }
261 };
262
263 block_on(fut)
264 }
265}