use crate::vfs::{VfsError, VfsResult, VirtualFileSystem};use std::collections::BTreeMap; const COMMAND_STUB: &[u8] = b"#!/bin/sh\n# kernel command stub\n"; #[derive(Debug, Clone, PartialEq, Eq)]pub struct CommandDriver { name: String, commands: Vec<String>,} impl CommandDriver { pub fn new<N, I, C>(name: N, commands: I) -> Self where N: Into<String>, I: IntoIterator<Item = C>, C: Into<String>, { Self { name: name.into(), commands: commands.into_iter().map(Into::into).collect(), } } pub fn name(&self) -> &str { &self.name } pub fn commands(&self) -> &[String] { &self.commands } fn validate_commands(&self) -> VfsResult<()> { for command in &self.commands { validate_command_name(command)?; } Ok(()) }} #[derive(Debug, Default, Clone)]pub struct CommandRegistry { commands: BTreeMap<String, CommandDriver>, warnings: Vec<String>,} impl CommandRegistry { pub fn new() -> Self { Self::default() } pub fn register(&mut self, driver: CommandDriver) -> VfsResult<()> { driver.validate_commands()?; for command in &driver.commands { if let Some(existing) = self.commands.get(command) { self.warnings.push(format!( "command \"{command}\" overridden: {} -> {}", existing.name(), driver.name() )); } self.commands.insert(command.clone(), driver.clone()); } Ok(()) } pub fn warnings(&self) -> &[String] { &self.warnings } pub fn resolve(&self, command: &str) -> Option<&CommandDriver> { self.commands.get(command) } pub fn list(&self) -> BTreeMap<String, String> { self.commands .iter() .map(|(command, driver)| (command.clone(), driver.name().to_owned())) .collect() } pub fn populate_bin<F>(&self, vfs: &mut F) -> VfsResult<()> where F: VirtualFileSystem, { self.populate_commands(vfs, self.commands.keys()) } pub fn populate_driver_bin<F>(&self, vfs: &mut F, driver: &CommandDriver) -> VfsResult<()> where F: VirtualFileSystem, { self.populate_commands(vfs, driver.commands()) } fn populate_commands<F, I, S>(&self, vfs: &mut F, commands: I) -> VfsResult<()> where F: VirtualFileSystem, I: IntoIterator<Item = S>, S: AsRef<str>, { let commands = commands .into_iter() .map(|command| { validate_command_name(command.as_ref())?; Ok(command.as_ref().to_owned()) }) .collect::<VfsResult<Vec<_>>>()?; if !vfs.exists("/bin") { vfs.mkdir("/bin", true)?; } for command in commands { let path = format!("/bin/{command}"); if !vfs.exists(&path) { vfs.write_file(&path, COMMAND_STUB.to_vec())?; let _ = vfs.chmod(&path, 0o755); } } Ok(()) }} fn validate_command_name(command: &str) -> VfsResult<()> { if command.is_empty() || command == "." || command == ".." || command.contains('/') || command.contains('\0') { return Err(VfsError::new( "EINVAL", format!("invalid command name {command:?}"), )); } Ok(())}
use crate::vfs::{VfsError, VfsResult, VirtualFileSystem};
use std::collections::BTreeMap;
const COMMAND_STUB: &[u8] = b"#!/bin/sh\n# kernel command stub\n";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandDriver {
name: String,
commands: Vec<String>,
}
impl CommandDriver {
pub fn new<N, I, C>(name: N, commands: I) -> Self
where
N: Into<String>,
I: IntoIterator<Item = C>,
C: Into<String>,
{
Self {
name: name.into(),
commands: commands.into_iter().map(Into::into).collect(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn commands(&self) -> &[String] {
&self.commands
}
fn validate_commands(&self) -> VfsResult<()> {
for command in &self.commands {
validate_command_name(command)?;
}
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct CommandRegistry {
commands: BTreeMap<String, CommandDriver>,
warnings: Vec<String>,
}
impl CommandRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, driver: CommandDriver) -> VfsResult<()> {
driver.validate_commands()?;
for command in &driver.commands {
if let Some(existing) = self.commands.get(command) {
self.warnings.push(format!(
"command \"{command}\" overridden: {} -> {}",
existing.name(),
driver.name()
));
}
self.commands.insert(command.clone(), driver.clone());
}
Ok(())
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn resolve(&self, command: &str) -> Option<&CommandDriver> {
self.commands.get(command)
}
pub fn list(&self) -> BTreeMap<String, String> {
self.commands
.iter()
.map(|(command, driver)| (command.clone(), driver.name().to_owned()))
.collect()
}
pub fn populate_bin<F>(&self, vfs: &mut F) -> VfsResult<()>
where
F: VirtualFileSystem,
{
self.populate_commands(vfs, self.commands.keys())
}
pub fn populate_driver_bin<F>(&self, vfs: &mut F, driver: &CommandDriver) -> VfsResult<()>
where
F: VirtualFileSystem,
{
self.populate_commands(vfs, driver.commands())
}
fn populate_commands<F, I, S>(&self, vfs: &mut F, commands: I) -> VfsResult<()>
where
F: VirtualFileSystem,
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let commands = commands
.into_iter()
.map(|command| {
validate_command_name(command.as_ref())?;
Ok(command.as_ref().to_owned())
})
.collect::<VfsResult<Vec<_>>>()?;
if !vfs.exists("/bin") {
vfs.mkdir("/bin", true)?;
}
for command in commands {
let path = format!("/bin/{command}");
if !vfs.exists(&path) {
vfs.write_file(&path, COMMAND_STUB.to_vec())?;
let _ = vfs.chmod(&path, 0o755);
}
}
Ok(())
}
}
fn validate_command_name(command: &str) -> VfsResult<()> {
if command.is_empty()
|| command == "."
|| command == ".."
|| command.contains('/')
|| command.contains('\0')
{
return Err(VfsError::new(
"EINVAL",
format!("invalid command name {command:?}"),
));
}
Ok(())
}