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, } impl CommandDriver { pub fn new(name: N, commands: I) -> Self where N: Into, I: IntoIterator, C: Into, { 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, warnings: Vec, } 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 { self.commands .iter() .map(|(command, driver)| (command.clone(), driver.name().to_owned())) .collect() } pub fn populate_bin(&self, vfs: &mut F) -> VfsResult<()> where F: VirtualFileSystem, { self.populate_commands(vfs, self.commands.keys()) } pub fn populate_driver_bin(&self, vfs: &mut F, driver: &CommandDriver) -> VfsResult<()> where F: VirtualFileSystem, { self.populate_commands(vfs, driver.commands()) } fn populate_commands(&self, vfs: &mut F, commands: I) -> VfsResult<()> where F: VirtualFileSystem, I: IntoIterator, S: AsRef, { let commands = commands .into_iter() .map(|command| { validate_command_name(command.as_ref())?; Ok(command.as_ref().to_owned()) }) .collect::>>()?; 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(()) }