Skip to main content

wasmer_wasix/os/command/builtins/
cmd_wasmer.rs

1use 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 the first argument is a '--' then skip it
103        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            // Set the arguments of the environment by replacing the state
111            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                // convert relative path to absolute path
120                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                    // Infer the command that is going to be executed
154                    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                    // Now run the module
177                    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        // Need to make sure this task runs on the main runtime.
193        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        // Read the command we want to run
224        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        // Check the command
231        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}