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, 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
28SUBCOMMANDS:
29 run Run a WebAssembly file. Formats accepted: wasm, wat
30"#;
31
32const HELP_RUN: &str = r#"USAGE:
33 wasmer run <FILE> [ARGS]...
34
35ARGS:
36 <FILE> File to run
37 <ARGS>... Application arguments
38"#;
39
40use crate::os::command::VirtualCommand;
41
42#[derive(Debug, Clone)]
43pub struct CmdWasmer {
44 runtime: Arc<dyn Runtime + Send + Sync + 'static>,
45}
46
47impl CmdWasmer {
48 const NAME: &'static str = "wasmer";
49
50 pub fn new(runtime: Arc<dyn Runtime + Send + Sync + 'static>) -> Self {
51 Self { runtime }
52 }
53}
54
55#[derive(Debug, Clone)]
56enum Executable {
57 Wasm(OwnedBuffer),
58 BinaryPackage(Box<BinaryPackage>),
59}
60
61impl CmdWasmer {
62 async fn run(
63 &self,
64 parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
65 name: &str,
66 config: &mut Option<WasiEnv>,
67 what: Option<String>,
68 mut args: Vec<String>,
69 ) -> Result<TaskJoinHandle, SpawnError> {
70 if args.first().map(|a| a.as_str()) == Some("--") {
72 args = args.into_iter().skip(1).collect();
73 }
74
75 if let Some(what) = what {
76 let mut env = config.take().ok_or(SpawnError::UnknownError)?;
77
78 let mut state = env.state.fork();
80 args.insert(0, what.clone());
81 state.args = std::sync::Mutex::new(args);
82 env.state = Arc::new(state);
83
84 let file_path = if what.starts_with('/') {
85 PathBuf::from(&what)
86 } else {
87 let cwd = env.state.fs.current_dir.lock().unwrap().clone();
89
90 PathBuf::from(cwd).join(&what)
91 };
92
93 let fs = env.fs_root();
94 let f = fs.new_open_options().read(true).open(&file_path);
95 let executable = if let Ok(mut file) = f {
96 let mut data = Vec::with_capacity(file.size() as usize);
97 file.read_to_end(&mut data).await.unwrap();
98
99 let bytes: bytes::Bytes = data.into();
100
101 if let Ok(container) = from_bytes(bytes.clone()) {
102 let pkg = BinaryPackage::from_webc(&container, &*self.runtime)
103 .await
104 .unwrap();
105
106 Executable::BinaryPackage(Box::new(pkg))
107 } else {
108 Executable::Wasm(OwnedBuffer::from_bytes(bytes))
109 }
110 } else if let Ok(pkg) = self.get_package(&what).await {
111 Executable::BinaryPackage(Box::new(pkg))
112 } else {
113 let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await;
114 let handle =
115 OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
116 return Ok(handle);
117 };
118
119 match executable {
120 Executable::BinaryPackage(binary) => {
121 let cmd_name: &str =
123 binary
124 .infer_entrypoint()
125 .map_err(|_| SpawnError::MissingEntrypoint {
126 package_id: binary.id.clone(),
127 })?;
128
129 {
130 let cmd =
131 binary
132 .get_command(cmd_name)
133 .ok_or_else(|| SpawnError::NotFound {
134 message: format!(
135 "{cmd_name} command in package: {}",
136 binary.id
137 ),
138 })?;
139 env.prepare_spawn(cmd);
140 }
141
142 env.use_package_async(&binary).await.unwrap();
143
144 spawn_exec(*binary, name, env, &self.runtime).await
146 }
147 Executable::Wasm(bytes) => {
148 let data = HashedModuleData::new(bytes);
149 spawn_exec_wasm(data, name, env, &self.runtime).await
150 }
151 }
152 } else {
153 let _ = unsafe { stderr_write(parent_ctx, HELP_RUN.as_bytes()) }.await;
154 let handle = OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
155 Ok(handle)
156 }
157 }
158
159 pub async fn get_package(&self, name: &str) -> Result<BinaryPackage, anyhow::Error> {
160 let (tx, rx) = tokio::sync::oneshot::channel();
162 let specifier = name.parse()?;
163 let rt = self.runtime.clone();
164 self.runtime.task_manager().task_shared(Box::new(|| {
165 Box::pin(async move {
166 let res = BinaryPackage::from_registry(&specifier, rt.as_ref()).await;
167 tx.send(res)
168 .expect("could not send response to output channel");
169 })
170 }))?;
171 rx.await
172 .map_err(|_| anyhow::anyhow!("package retrieval response channel died"))?
173 }
174}
175
176impl VirtualCommand for CmdWasmer {
177 fn name(&self) -> &str {
178 Self::NAME
179 }
180
181 fn as_any(&self) -> &dyn Any {
182 self
183 }
184
185 fn exec(
186 &self,
187 parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
188 name: &str,
189 env: &mut Option<WasiEnv>,
190 ) -> Result<TaskJoinHandle, SpawnError> {
191 let env_inner = env.as_ref().ok_or(SpawnError::UnknownError)?;
193 let args = env_inner.state.args.lock().unwrap().clone();
194 let mut args = args.iter().map(|s| s.as_str());
195 let _alias = args.next();
196 let cmd = args.next();
197
198 let fut = async {
200 match cmd {
201 Some("run") => {
202 let what = args.next().map(|a| a.to_string());
203 let args = args.map(|a| a.to_string()).collect();
204 self.run(parent_ctx, name, env, what, args).await
205 }
206 Some("--help") | None => {
207 unsafe { stderr_write(parent_ctx, HELP.as_bytes()) }
208 .await
209 .ok();
210 let handle =
211 OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle();
212 Ok(handle)
213 }
214 Some(what) => {
215 let what = Some(what.to_string());
216 let args = args.map(|a| a.to_string()).collect();
217 self.run(parent_ctx, name, env, what, args).await
218 }
219 }
220 };
221
222 block_on(fut)
223 }
224}