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, 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 the first argument is a '--' then skip it
71        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            // Set the arguments of the environment by replacing the state
79            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                // convert relative path to absolute path
88                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                    // Infer the command that is going to be executed
122                    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                    // Now run the module
145                    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        // Need to make sure this task runs on the main runtime.
161        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        // Read the command we want to run
192        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        // Check the command
199        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}