Skip to main content

secure_exec_kernel/
command_registry.rs

1use crate::vfs::{VfsError, VfsResult, VirtualFileSystem};
2use std::collections::BTreeMap;
3
4const COMMAND_STUB: &[u8] = b"#!/bin/sh\n# kernel command stub\n";
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct CommandDriver {
8    name: String,
9    commands: Vec<String>,
10}
11
12impl CommandDriver {
13    pub fn new<N, I, C>(name: N, commands: I) -> Self
14    where
15        N: Into<String>,
16        I: IntoIterator<Item = C>,
17        C: Into<String>,
18    {
19        Self {
20            name: name.into(),
21            commands: commands.into_iter().map(Into::into).collect(),
22        }
23    }
24
25    pub fn name(&self) -> &str {
26        &self.name
27    }
28
29    pub fn commands(&self) -> &[String] {
30        &self.commands
31    }
32
33    fn validate_commands(&self) -> VfsResult<()> {
34        for command in &self.commands {
35            validate_command_name(command)?;
36        }
37
38        Ok(())
39    }
40}
41
42#[derive(Debug, Default, Clone)]
43pub struct CommandRegistry {
44    commands: BTreeMap<String, CommandDriver>,
45    warnings: Vec<String>,
46}
47
48impl CommandRegistry {
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    pub fn register(&mut self, driver: CommandDriver) -> VfsResult<()> {
54        driver.validate_commands()?;
55
56        for command in &driver.commands {
57            if let Some(existing) = self.commands.get(command) {
58                self.warnings.push(format!(
59                    "command \"{command}\" overridden: {} -> {}",
60                    existing.name(),
61                    driver.name()
62                ));
63            }
64
65            self.commands.insert(command.clone(), driver.clone());
66        }
67
68        Ok(())
69    }
70
71    pub fn warnings(&self) -> &[String] {
72        &self.warnings
73    }
74
75    pub fn resolve(&self, command: &str) -> Option<&CommandDriver> {
76        self.commands.get(command)
77    }
78
79    pub fn list(&self) -> BTreeMap<String, String> {
80        self.commands
81            .iter()
82            .map(|(command, driver)| (command.clone(), driver.name().to_owned()))
83            .collect()
84    }
85
86    pub fn populate_bin<F>(&self, vfs: &mut F) -> VfsResult<()>
87    where
88        F: VirtualFileSystem,
89    {
90        self.populate_commands(vfs, self.commands.keys())
91    }
92
93    pub fn populate_driver_bin<F>(&self, vfs: &mut F, driver: &CommandDriver) -> VfsResult<()>
94    where
95        F: VirtualFileSystem,
96    {
97        self.populate_commands(vfs, driver.commands())
98    }
99
100    fn populate_commands<F, I, S>(&self, vfs: &mut F, commands: I) -> VfsResult<()>
101    where
102        F: VirtualFileSystem,
103        I: IntoIterator<Item = S>,
104        S: AsRef<str>,
105    {
106        let commands = commands
107            .into_iter()
108            .map(|command| {
109                validate_command_name(command.as_ref())?;
110                Ok(command.as_ref().to_owned())
111            })
112            .collect::<VfsResult<Vec<_>>>()?;
113
114        if !vfs.exists("/bin") {
115            vfs.mkdir("/bin", true)?;
116        }
117
118        for command in commands {
119            let path = format!("/bin/{command}");
120            if !vfs.exists(&path) {
121                vfs.write_file(&path, COMMAND_STUB.to_vec())?;
122                let _ = vfs.chmod(&path, 0o755);
123            }
124        }
125
126        Ok(())
127    }
128}
129
130fn validate_command_name(command: &str) -> VfsResult<()> {
131    if command.is_empty()
132        || command == "."
133        || command == ".."
134        || command.contains('/')
135        || command.contains('\0')
136    {
137        return Err(VfsError::new(
138            "EINVAL",
139            format!("invalid command name {command:?}"),
140        ));
141    }
142
143    Ok(())
144}