wasmer_wasix/os/command/
mod.rs

1pub mod builtins;
2
3use std::{collections::HashMap, sync::Arc};
4
5use virtual_mio::block_on;
6use wasmer::FunctionEnvMut;
7use wasmer_wasix_types::wasi::Errno;
8
9use crate::{Runtime, SpawnError, WasiEnv, syscalls::stderr_write};
10
11use super::task::{OwnedTaskStatus, TaskJoinHandle, TaskStatus};
12
13/// A command available to an OS environment.
14pub trait VirtualCommand
15where
16    Self: std::fmt::Debug,
17{
18    /// Returns the canonical name of the command.
19    fn name(&self) -> &str;
20
21    /// Retrieve the command as a [`std::any::Any`] reference.
22    fn as_any(&self) -> &dyn std::any::Any;
23
24    /// Executes the command.
25    fn exec(
26        &self,
27        parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
28        path: &str,
29        config: &mut Option<WasiEnv>,
30    ) -> Result<TaskJoinHandle, SpawnError>;
31}
32
33#[derive(Debug, Clone)]
34pub struct Commands {
35    commands: HashMap<String, Arc<dyn VirtualCommand + Send + Sync + 'static>>,
36}
37
38impl Commands {
39    fn new() -> Self {
40        Self {
41            commands: HashMap::new(),
42        }
43    }
44
45    // TODO: this method should be somewhere on the runtime, not here.
46    pub fn new_with_builtins(runtime: Arc<dyn Runtime + Send + Sync + 'static>) -> Self {
47        let mut cmd = Self::new();
48        let cmd_wasmer = builtins::cmd_wasmer::CmdWasmer::new(runtime.clone());
49        cmd.register_command(cmd_wasmer);
50
51        cmd
52    }
53
54    /// Register a command.
55    ///
56    /// The command will be available with it's canonical name ([`VirtualCommand::name()`]) at /bin/NAME.
57    pub fn register_command<C: VirtualCommand + Send + Sync + 'static>(&mut self, cmd: C) {
58        let path = format!("/bin/{}", cmd.name());
59        self.register_command_with_path(cmd, path);
60    }
61
62    /// Register a command at a custom path.
63    pub fn register_command_with_path<C: VirtualCommand + Send + Sync + 'static>(
64        &mut self,
65        cmd: C,
66        path: String,
67    ) {
68        self.commands.insert(path, Arc::new(cmd));
69    }
70
71    /// Determine if a command exists at the given path.
72    pub fn exists(&self, path: &str) -> bool {
73        let name = path.to_string();
74        self.commands.contains_key(&name)
75    }
76
77    /// Get a command by its path.
78    pub fn get(&self, path: &str) -> Option<&Arc<dyn VirtualCommand + Send + Sync + 'static>> {
79        self.commands.get(path)
80    }
81
82    /// Execute a command.
83    pub fn exec(
84        &self,
85        parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
86        path: &str,
87        builder: &mut Option<WasiEnv>,
88    ) -> Result<TaskJoinHandle, SpawnError> {
89        let path = path.to_string();
90        if let Some(cmd) = self.commands.get(&path) {
91            cmd.exec(parent_ctx, path.as_str(), builder)
92        } else {
93            unsafe {
94                block_on(stderr_write(
95                    parent_ctx,
96                    format!("wasm command unknown - {path}\r\n").as_bytes(),
97                ))
98            }
99            .ok();
100
101            let res = OwnedTaskStatus::new(TaskStatus::Finished(Ok(Errno::Noent.into())));
102            Ok(res.handle())
103        }
104    }
105}