diff --git a/common/Cargo.toml b/common/Cargo.toml index 8022201..bad944a 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -25,6 +25,7 @@ nix = { version = "0.30.1", features = ["mman"], optional = true } async-channel = { version = "2.5.0", default-features = false } async-lock = { version = "3.4.1", default-features = false } serde = { version = "1.0.228", default-features = false, features = ["alloc", "derive"] } +embedded-io = { version = "0.6", default-features = false } [target.'cfg(target_os="linux")'.dev-dependencies] rand = "0.9.2" diff --git a/common/src/protocol/control.rs b/common/src/protocol/control.rs index b29173c..a6d315c 100644 --- a/common/src/protocol/control.rs +++ b/common/src/protocol/control.rs @@ -4,11 +4,14 @@ use alloc::vec::Vec; use serde::{Deserialize, Serialize}; +pub use embedded_io::ErrorKind; + #[derive(Debug, Serialize, Deserialize)] pub enum Request { GetArgs, Exit(i32), Open(String, FileMode), + Mkdir(String), Listen { ip: [u8; 4], port: u16 }, Connect { host: String, port: u16 }, } @@ -17,6 +20,57 @@ pub enum Request { pub enum Response { Args(Vec), Pipe(PipeData), + Ack, + Err(IoErrorKind), +} + +/// Wire-serializable mirror of [`embedded_io::ErrorKind`], which is +/// `#[non_exhaustive]` and not `serde`-derivable. Carries a host I/O failure +/// back to the guest, where it converts into an `embedded_io::ErrorKind`. +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum IoErrorKind { + NotFound, + PermissionDenied, + AlreadyExists, + InvalidInput, + InvalidData, + Unsupported, + OutOfMemory, + Interrupted, + Other, +} + +impl From for ErrorKind { + fn from(e: IoErrorKind) -> Self { + match e { + IoErrorKind::NotFound => ErrorKind::NotFound, + IoErrorKind::PermissionDenied => ErrorKind::PermissionDenied, + IoErrorKind::AlreadyExists => ErrorKind::AlreadyExists, + IoErrorKind::InvalidInput => ErrorKind::InvalidInput, + IoErrorKind::InvalidData => ErrorKind::InvalidData, + IoErrorKind::Unsupported => ErrorKind::Unsupported, + IoErrorKind::OutOfMemory => ErrorKind::OutOfMemory, + IoErrorKind::Interrupted => ErrorKind::Interrupted, + IoErrorKind::Other => ErrorKind::Other, + } + } +} + +#[cfg(feature = "std")] +impl From for IoErrorKind { + fn from(e: std::io::ErrorKind) -> Self { + match e { + std::io::ErrorKind::NotFound => IoErrorKind::NotFound, + std::io::ErrorKind::PermissionDenied => IoErrorKind::PermissionDenied, + std::io::ErrorKind::AlreadyExists => IoErrorKind::AlreadyExists, + std::io::ErrorKind::InvalidInput => IoErrorKind::InvalidInput, + std::io::ErrorKind::InvalidData => IoErrorKind::InvalidData, + std::io::ErrorKind::Unsupported => IoErrorKind::Unsupported, + std::io::ErrorKind::OutOfMemory => IoErrorKind::OutOfMemory, + std::io::ErrorKind::Interrupted => IoErrorKind::Interrupted, + _ => IoErrorKind::Other, + } + } } #[derive(Debug, Serialize, Deserialize)] diff --git a/fix/.cargo/config.toml b/fix/.cargo/config.toml index d1ab66c..34803b4 100644 --- a/fix/.cargo/config.toml +++ b/fix/.cargo/config.toml @@ -5,4 +5,4 @@ rustflags = [ "-C", "relocation-model=static", # "-C", "target-feature=+crt-static", ] -runner = "cargo run -p evaluator --bin fix-cli -- hybrid" +runner = "cargo run -p vmm --" diff --git a/fix/src/main.rs b/fix/src/main.rs index 1d8435e..a3b96db 100644 --- a/fix/src/main.rs +++ b/fix/src/main.rs @@ -1,6 +1,6 @@ #![no_main] #![no_std] -use kernel::host::fs::{File, Whence}; +use kernel::host::fs::{self, File, Whence}; use kernel::host::os; use kernel::prelude::*; @@ -16,8 +16,36 @@ use derive_more::Unwrap; #[kmain] fn main() { let argv = os::argv(); - let filename = argv.get(1).expect("expected command file"); + // Subcommand dispatch: `fix init` | `fix eval `. + match argv.get(1).map(String::as_str) { + Some("init") => init(), + Some("eval") => { + let filename = argv.get(2).expect("fix eval: expected a command file"); + eval_file(filename); + } + Some(other) => panic!("fix: unknown command '{other}' (expected: init | eval )"), + None => panic!("fix: expected a command (init | eval )"), + } + + kernel::shutdown(); +} + +/// `fix init`: create the on-disk `.fix` store with its `objects/` and +/// `labels/` subdirs. `mkdir` maps to host `create_dir_all`, so re-running on an +/// existing store is harmless (matches git's "reinitialized existing repository"). +fn init() { + for dir in [".fix/objects", ".fix/labels"] { + if let Err(e) = fs::mkdir(dir) { + println!("fix init: failed to create {dir}: {e:?}"); + kernel::exit(1); + } + } + println!("initialized empty fix store in .fix"); +} + +/// `fix eval `: read, parse, and evaluate a command file. +fn eval_file(filename: &str) { let mut file = File::open(filename, true, false, false, false, false).unwrap(); let len = file.seek(Whence::End(0)) as usize; file.seek(Whence::Start(0)); @@ -73,8 +101,6 @@ fn main() { } } } - - kernel::shutdown(); } #[derive(Clone, Debug, Unwrap)] diff --git a/kernel/src/host.rs b/kernel/src/host.rs index 1a12ca6..e43a7fb 100644 --- a/kernel/src/host.rs +++ b/kernel/src/host.rs @@ -238,12 +238,28 @@ pub mod fs { use super::get_pipe; use crate::pipe::*; pub use common::protocol::file::Whence; - use common::{protocol::control::FileMode, protocol::*}; + use common::{ + protocol::control::{ErrorKind, FileMode}, + protocol::*, + }; pub struct File { pipe: FilePipe, } + /// Recursively creates a directory and all of its parents on the host, like + /// `mkdir -p`. Mirrors [`File::open`], but the host has nothing to stream + /// back, so success is a bare ack and failure carries an [`ErrorKind`]. + pub fn mkdir(path: &str) -> Result<(), ErrorKind> { + let mut binding = crate::pipe::HOST.lock(); + let host = binding.get_mut().unwrap(); + match host.request(&control::Request::Mkdir(path.into())) { + control::Response::Ack => Ok(()), + control::Response::Err(e) => Err(e.into()), + _ => Err(ErrorKind::Other), + } + } + impl File { pub fn open( path: &str, @@ -252,10 +268,10 @@ pub mod fs { create: bool, append: bool, truncate: bool, - ) -> Option { + ) -> Result { let mut binding = crate::pipe::HOST.lock(); let host = binding.get_mut().unwrap(); - let control::Response::Pipe(id) = host.request(&control::Request::Open( + match host.request(&control::Request::Open( path.into(), FileMode { read, @@ -264,13 +280,14 @@ pub mod fs { append, truncate, }, - )) else { - return None; - }; - unsafe { - Some(File { - pipe: FilePipe::new(get_pipe(id)), - }) + )) { + control::Response::Pipe(id) => unsafe { + Ok(File { + pipe: FilePipe::new(get_pipe(id)), + }) + }, + control::Response::Err(e) => Err(e.into()), + _ => Err(ErrorKind::Other), } } diff --git a/vmm/src/comm.rs b/vmm/src/comm.rs index f5fd2aa..860d1b1 100644 --- a/vmm/src/comm.rs +++ b/vmm/src/comm.rs @@ -45,9 +45,13 @@ pub fn control_thread(argv: Vec, mut pipe: ControlPipe) { }); Response::Pipe(decompose_pipe(p)) } - Err(x) => todo!("open: {x:?}"), + Err(e) => Response::Err(e.kind().into()), } } + Request::Mkdir(path) => match std::fs::create_dir_all(&path) { + Ok(()) => Response::Ack, + Err(e) => Response::Err(e.kind().into()), + }, Request::Listen { ip, port } => { let listener = TcpListener::bind(SocketAddr::from((ip, port))).unwrap(); let (p, q) = common::pipe::pipe(1024);