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