diff --git a/hypha/Cargo.toml b/hypha/Cargo.toml index a7f8aaf..0753d64 100644 --- a/hypha/Cargo.toml +++ b/hypha/Cargo.toml @@ -24,6 +24,7 @@ sha256 = "1.5.0" url = "2.5.0" ureq = "2.9.1" path-clean = "1.0.1" +termion = "3.0.0" [dependencies.xenstore] path = "../xenstore" diff --git a/hypha/bin/controller.rs b/hypha/bin/controller.rs index 79e0b10..6f29651 100644 --- a/hypha/bin/controller.rs +++ b/hypha/bin/controller.rs @@ -6,12 +6,6 @@ use std::path::PathBuf; #[derive(Parser, Debug)] #[command(version, about)] struct ControllerArgs { - #[arg(short, long)] - kernel: String, - - #[arg(short = 'r', long)] - initrd: String, - #[arg(short, long, default_value = "auto")] store: String, @@ -22,6 +16,10 @@ struct ControllerArgs { #[derive(Subcommand, Debug)] enum Commands { Launch { + #[arg(short, long)] + kernel: String, + #[arg(short = 'r', long)] + initrd: String, #[arg(short, long)] image: String, #[arg(short, long, default_value_t = 1)] @@ -33,6 +31,10 @@ enum Commands { #[arg(short, long)] domain: u32, }, + Console { + #[arg(short, long)] + domain: u32, + }, } fn main() -> Result<()> { @@ -51,16 +53,27 @@ fn main() -> Result<()> { .map(|x| x.to_string()) .ok_or_else(|| HyphaError::new("unable to convert store path to string"))?; - let mut controller = Controller::new(store_path, args.kernel, args.initrd)?; + let mut controller = Controller::new(store_path)?; match args.command { - Commands::Launch { image, cpus, mem } => { - let domid = controller.launch(image.as_str(), cpus, mem)?; + Commands::Launch { + kernel, + initrd, + image, + cpus, + mem, + } => { + let domid = controller.launch(&kernel, &initrd, &image, cpus, mem)?; println!("launched domain: {}", domid); } + Commands::Destroy { domain } => { controller.destroy(domain)?; } + + Commands::Console { domain } => { + controller.console(domain)?; + } } Ok(()) } diff --git a/hypha/src/ctl/mod.rs b/hypha/src/ctl/mod.rs index 123937c..efc1450 100644 --- a/hypha/src/ctl/mod.rs +++ b/hypha/src/ctl/mod.rs @@ -7,8 +7,11 @@ use crate::image::cache::ImageCache; use crate::image::name::ImageName; use crate::image::{ImageCompiler, ImageInfo}; use loopdev::LoopControl; -use std::fs; +use std::io::{Read, Write}; use std::path::PathBuf; +use std::process::exit; +use std::{fs, io, thread}; +use termion::raw::IntoRawMode; use uuid::Uuid; use xenclient::{DomainConfig, DomainDisk, XenClient}; use xenstore::client::{XsdClient, XsdInterface}; @@ -17,12 +20,10 @@ pub struct Controller { image_cache: ImageCache, autoloop: AutoLoop, client: XenClient, - kernel_path: String, - initrd_path: String, } impl Controller { - pub fn new(store_path: String, kernel_path: String, initrd_path: String) -> Result { + pub fn new(store_path: String) -> Result { let mut image_cache_path = PathBuf::from(store_path); image_cache_path.push("cache"); fs::create_dir_all(&image_cache_path)?; @@ -35,8 +36,6 @@ impl Controller { image_cache, autoloop: AutoLoop::new(LoopControl::open()?), client, - kernel_path, - initrd_path, }) } @@ -46,7 +45,14 @@ impl Controller { compiler.compile(&image) } - pub fn launch(&mut self, image: &str, vcpus: u32, mem: u64) -> Result { + pub fn launch( + &mut self, + kernel_path: &str, + initrd_path: &str, + image: &str, + vcpus: u32, + mem: u64, + ) -> Result { let uuid = Uuid::new_v4(); let name = format!("hypha-{uuid}"); let image_info = self.compile(image)?; @@ -64,8 +70,8 @@ impl Controller { name: &name, max_vcpus: vcpus, mem_mb: mem, - kernel_path: self.kernel_path.as_str(), - initrd_path: self.initrd_path.as_str(), + kernel_path, + initrd_path, cmdline: "quiet elevator=noop", disks: vec![ DomainDisk { @@ -130,4 +136,45 @@ impl Controller { } Ok(uuid) } + + pub fn console(&mut self, domid: u32) -> Result<()> { + let (mut read, mut write) = self.client.open_console(domid)?; + let mut stdin = io::stdin(); + let is_tty = termion::is_tty(&stdin); + let mut stdout_for_exit = io::stdout().into_raw_mode()?; + thread::spawn(move || { + let mut buffer = vec![0u8; 60]; + loop { + let size = stdin.read(&mut buffer).expect("failed to read stdin"); + if is_tty && size == 1 && buffer[0] == 0x1d { + stdout_for_exit + .suspend_raw_mode() + .expect("failed to disable raw mode"); + stdout_for_exit.flush().expect("failed to flush stdout"); + exit(0); + } + write + .write_all(&buffer[0..size]) + .expect("failed to write to domain console"); + write.flush().expect("failed to flush domain console"); + } + }); + + let mut buffer = vec![0u8; 256]; + if is_tty { + let mut stdout = io::stdout().into_raw_mode()?; + loop { + let size = read.read(&mut buffer)?; + stdout.write_all(&buffer[0..size])?; + stdout.flush()?; + } + } else { + let mut stdout = io::stdout(); + loop { + let size = read.read(&mut buffer)?; + stdout.write_all(&buffer[0..size])?; + stdout.flush()?; + } + } + } } diff --git a/hypha/src/image/fetch.rs b/hypha/src/image/fetch.rs index 9d5eb0c..c2855ec 100644 --- a/hypha/src/image/fetch.rs +++ b/hypha/src/image/fetch.rs @@ -52,16 +52,19 @@ impl RegistryClient { .url .join(&format!("/v2/{}/manifests/{}", name, reference))?; let accept = format!( - "{}, {}, {}", + "{}, {}, {}, {}", MediaType::ImageManifest.to_docker_v2s2()?, MediaType::ImageManifest, MediaType::ImageIndex, + MediaType::ImageIndex.to_docker_v2s2()?, ); let response = self.call(self.agent.get(url.as_str()).set("Accept", &accept))?; let content_type = response.header("Content-Type").ok_or_else(|| { HyphaError::new("registry response did not have a Content-Type header") })?; - if content_type == MediaType::ImageIndex.to_string() { + if content_type == MediaType::ImageIndex.to_string() + || content_type == MediaType::ImageIndex.to_docker_v2s2()? + { let index = ImageIndex::from_reader(response.into_reader())?; let descriptor = self .pick_manifest(index) diff --git a/xenclient/src/lib.rs b/xenclient/src/lib.rs index 4966ede..2f332ca 100644 --- a/xenclient/src/lib.rs +++ b/xenclient/src/lib.rs @@ -10,7 +10,7 @@ use crate::x86::X86BootSetup; use log::warn; use std::error::Error; use std::fmt::{Display, Formatter}; -use std::fs::read; +use std::fs::{read, File, OpenOptions}; use std::path::PathBuf; use std::str::FromStr; use std::string::FromUtf8Error; @@ -561,4 +561,22 @@ impl XenClient { tx.commit()?; Ok(()) } + + pub fn open_console(&mut self, domid: u32) -> Result<(File, File), XenClientError> { + let dom_path = self.store.get_domain_path(domid)?; + let console_tty_path = format!("{}/console/tty", dom_path); + let tty = self + .store + .read_string_optional(&console_tty_path)? + .unwrap_or("".to_string()); + if tty.is_empty() { + return Err(XenClientError::new(&format!( + "domain {} does not have a tty", + domid + ))); + } + let read = OpenOptions::new().read(true).write(false).open(&tty)?; + let write = OpenOptions::new().read(false).write(true).open(&tty)?; + Ok((read, write)) + } }