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, 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 the first argument is a '--' then skip it
70        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            // Set the arguments of the environment by replacing the state
78            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                // convert relative path to absolute path
87                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                    // Infer the command that is going to be executed
121                    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                    // Now run the module
144                    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        // Need to make sure this task runs on the main runtime.
160        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        // Read the command we want to run
191        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        // Check the command
198        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}