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
13type BuiltinCommandHandler = dyn for<'a> Fn(
14        &FunctionEnvMut<'a, WasiEnv>,
15        &str,
16        &mut Option<WasiEnv>,
17    ) -> Result<TaskJoinHandle, SpawnError>
18    + Send
19    + Sync
20    + 'static;
21
22#[derive(Clone)]
23pub struct BuiltinCommand {
24    name: String,
25    handler: Arc<BuiltinCommandHandler>,
26}
27
28impl std::fmt::Debug for BuiltinCommand {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("BuiltinCommand")
31            .field("name", &self.name)
32            .finish()
33    }
34}
35
36impl BuiltinCommand {
37    pub fn new<Name, Handler>(name: Name, handler: Handler) -> Self
38    where
39        Name: Into<String>,
40        Handler: for<'a> Fn(
41                &FunctionEnvMut<'a, WasiEnv>,
42                &str,
43                &mut Option<WasiEnv>,
44            ) -> Result<TaskJoinHandle, SpawnError>
45            + Send
46            + Sync
47            + 'static,
48    {
49        Self {
50            name: name.into(),
51            handler: Arc::new(handler),
52        }
53    }
54}
55
56impl VirtualCommand for BuiltinCommand {
57    fn name(&self) -> &str {
58        self.name.as_str()
59    }
60
61    fn as_any(&self) -> &dyn std::any::Any {
62        self
63    }
64
65    fn exec(
66        &self,
67        parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
68        path: &str,
69        config: &mut Option<WasiEnv>,
70    ) -> Result<TaskJoinHandle, SpawnError> {
71        (self.handler)(parent_ctx, path, config)
72    }
73}
74
75/// A command available to an OS environment.
76pub trait VirtualCommand
77where
78    Self: std::fmt::Debug,
79{
80    /// Returns the canonical name of the command.
81    fn name(&self) -> &str;
82
83    /// Retrieve the command as a [`std::any::Any`] reference.
84    fn as_any(&self) -> &dyn std::any::Any;
85
86    /// Executes the command.
87    fn exec(
88        &self,
89        parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
90        path: &str,
91        config: &mut Option<WasiEnv>,
92    ) -> Result<TaskJoinHandle, SpawnError>;
93}
94
95#[derive(Debug, Clone)]
96pub struct Commands {
97    commands: HashMap<String, Arc<dyn VirtualCommand + Send + Sync + 'static>>,
98}
99
100impl Commands {
101    fn new() -> Self {
102        Self {
103            commands: HashMap::new(),
104        }
105    }
106
107    // TODO: this method should be somewhere on the runtime, not here.
108    pub fn new_with_builtins(runtime: Arc<dyn Runtime + Send + Sync + 'static>) -> Self {
109        let mut cmd = Self::new();
110        let cmd_wasmer = builtins::cmd_wasmer::CmdWasmer::new(runtime.clone());
111        cmd.register_command(cmd_wasmer);
112
113        cmd
114    }
115
116    /// Register a command.
117    ///
118    /// The command will be available with it's canonical name ([`VirtualCommand::name()`]) at /bin/NAME.
119    pub fn register_command<C: VirtualCommand + Send + Sync + 'static>(&mut self, cmd: C) {
120        self.register_command_shared(Arc::new(cmd));
121    }
122
123    /// Register a command at a custom path.
124    pub fn register_command_with_path<C: VirtualCommand + Send + Sync + 'static>(
125        &mut self,
126        cmd: C,
127        path: String,
128    ) {
129        self.register_command_with_path_shared(Arc::new(cmd), path);
130    }
131
132    /// Register a command behind an [`Arc`].
133    pub(crate) fn register_command_shared(
134        &mut self,
135        cmd: Arc<dyn VirtualCommand + Send + Sync + 'static>,
136    ) {
137        let path = format!("/bin/{}", cmd.name());
138        self.register_command_with_path_shared(cmd, path);
139    }
140
141    /// Register a command behind an [`Arc`] at a custom path.
142    pub(crate) fn register_command_with_path_shared(
143        &mut self,
144        cmd: Arc<dyn VirtualCommand + Send + Sync + 'static>,
145        path: String,
146    ) {
147        self.commands.insert(path, cmd);
148    }
149
150    /// Remove all registered commands.
151    pub fn clear(&mut self) {
152        self.commands.clear();
153    }
154
155    /// Determine if a command exists at the given path.
156    pub fn exists(&self, path: &str) -> bool {
157        let name = path.to_string();
158        self.commands.contains_key(&name)
159    }
160
161    /// Get a command by its path.
162    pub fn get(&self, path: &str) -> Option<&Arc<dyn VirtualCommand + Send + Sync + 'static>> {
163        self.commands.get(path)
164    }
165
166    /// Execute a command.
167    pub fn exec(
168        &self,
169        parent_ctx: &FunctionEnvMut<'_, WasiEnv>,
170        path: &str,
171        builder: &mut Option<WasiEnv>,
172    ) -> Result<TaskJoinHandle, SpawnError> {
173        let path = path.to_string();
174        if let Some(cmd) = self.commands.get(&path) {
175            cmd.exec(parent_ctx, path.as_str(), builder)
176        } else {
177            unsafe {
178                block_on(stderr_write(
179                    parent_ctx,
180                    format!("wasm command unknown - {path}\r\n").as_bytes(),
181                ))
182            }
183            .ok();
184
185            let res = OwnedTaskStatus::new(TaskStatus::Finished(Ok(Errno::Noent.into())));
186            Ok(res.handle())
187        }
188    }
189}