diff --git a/hypha/Cargo.toml b/hypha/Cargo.toml index 4dbe6bf..a7f8aaf 100644 --- a/hypha/Cargo.toml +++ b/hypha/Cargo.toml @@ -25,6 +25,9 @@ url = "2.5.0" ureq = "2.9.1" path-clean = "1.0.1" +[dependencies.xenstore] +path = "../xenstore" + [dependencies.clap] version = "4.4.18" features = ["derive"] diff --git a/hypha/bin/controller.rs b/hypha/bin/controller.rs index da27b77..79e0b10 100644 --- a/hypha/bin/controller.rs +++ b/hypha/bin/controller.rs @@ -1,4 +1,4 @@ -use clap::Parser; +use clap::{Parser, Subcommand}; use hypha::ctl::Controller; use hypha::error::{HyphaError, Result}; use std::path::PathBuf; @@ -12,17 +12,27 @@ struct ControllerArgs { #[arg(short = 'r', long)] initrd: String, - #[arg(short, long)] - image: String, - - #[arg(short, long, default_value_t = 1)] - cpus: u32, - - #[arg(short, long, default_value_t = 512)] - mem: u64, - #[arg(short, long, default_value = "auto")] store: String, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + Launch { + #[arg(short, long)] + image: String, + #[arg(short, long, default_value_t = 1)] + cpus: u32, + #[arg(short, long, default_value_t = 512)] + mem: u64, + }, + Destroy { + #[arg(short, long)] + domain: u32, + }, } fn main() -> Result<()> { @@ -41,16 +51,17 @@ 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, - args.image, - args.cpus, - args.mem, - )?; - let domid = controller.launch()?; - println!("launched domain: {}", domid); + let mut controller = Controller::new(store_path, args.kernel, args.initrd)?; + + match args.command { + Commands::Launch { image, cpus, mem } => { + let domid = controller.launch(image.as_str(), cpus, mem)?; + println!("launched domain: {}", domid); + } + Commands::Destroy { domain } => { + controller.destroy(domain)?; + } + } Ok(()) } diff --git a/hypha/src/ctl/mod.rs b/hypha/src/ctl/mod.rs index a1d151d..123937c 100644 --- a/hypha/src/ctl/mod.rs +++ b/hypha/src/ctl/mod.rs @@ -2,7 +2,7 @@ pub mod cfgblk; use crate::autoloop::AutoLoop; use crate::ctl::cfgblk::ConfigBlock; -use crate::error::Result; +use crate::error::{HyphaError, Result}; use crate::image::cache::ImageCache; use crate::image::name::ImageName; use crate::image::{ImageCompiler, ImageInfo}; @@ -11,27 +11,18 @@ use std::fs; use std::path::PathBuf; use uuid::Uuid; use xenclient::{DomainConfig, DomainDisk, XenClient}; +use xenstore::client::{XsdClient, XsdInterface}; pub struct Controller { image_cache: ImageCache, autoloop: AutoLoop, - image: String, client: XenClient, kernel_path: String, initrd_path: String, - vcpus: u32, - mem: u64, } impl Controller { - pub fn new( - store_path: String, - kernel_path: String, - initrd_path: String, - image: String, - vcpus: u32, - mem: u64, - ) -> Result { + pub fn new(store_path: String, kernel_path: String, initrd_path: String) -> Result { let mut image_cache_path = PathBuf::from(store_path); image_cache_path.push("cache"); fs::create_dir_all(&image_cache_path)?; @@ -43,25 +34,22 @@ impl Controller { Ok(Controller { image_cache, autoloop: AutoLoop::new(LoopControl::open()?), - image, client, kernel_path, initrd_path, - vcpus, - mem, }) } - fn compile(&mut self) -> Result { - let image = ImageName::parse(&self.image)?; + fn compile(&mut self, image: &str) -> Result { + let image = ImageName::parse(image)?; let compiler = ImageCompiler::new(&self.image_cache)?; compiler.compile(&image) } - pub fn launch(&mut self) -> Result { + pub fn launch(&mut self, image: &str, vcpus: u32, mem: u64) -> Result { let uuid = Uuid::new_v4(); let name = format!("hypha-{uuid}"); - let image_info = self.compile()?; + let image_info = self.compile(image)?; let cfgblk = ConfigBlock::new(&uuid, &image_info)?; cfgblk.build()?; @@ -74,8 +62,8 @@ impl Controller { let config = DomainConfig { backend_domid: 0, name: &name, - max_vcpus: self.vcpus, - mem_mb: self.mem, + max_vcpus: vcpus, + mem_mb: mem, kernel_path: self.kernel_path.as_str(), initrd_path: self.initrd_path.as_str(), cmdline: "quiet elevator=noop", @@ -91,6 +79,16 @@ impl Controller { writable: false, }, ], + extra_keys: vec![ + ("hypha/uuid".to_string(), uuid.to_string()), + ( + "hypha/loops".to_string(), + format!( + "{},{}", + &image_squashfs_loop.path, &cfgblk_squashfs_loop.path + ), + ), + ], }; match self.client.create(&config) { Ok(domid) => Ok(domid), @@ -102,4 +100,34 @@ impl Controller { } } } + + pub fn destroy(&mut self, domid: u32) -> Result { + let mut store = XsdClient::open()?; + let dom_path = store.get_domain_path(domid)?; + let uuid = match store.read_string_optional(format!("{}/hypha/uuid", dom_path).as_str())? { + None => { + return Err(HyphaError::new(&format!( + "domain {} was not found or not created by hypha", + domid + ))) + } + Some(value) => value, + }; + if uuid.is_empty() { + return Err(HyphaError::new( + "unable to find hypha uuid based on the domain", + )); + } + let uuid = Uuid::parse_str(&uuid)?; + let loops = store.read_string(format!("{}/hypha/loops", dom_path).as_str())?; + let loops = loops + .split(',') + .map(|x| x.to_string()) + .collect::>(); + self.client.destroy(domid)?; + for lop in &loops { + self.autoloop.unloop(lop)?; + } + Ok(uuid) + } } diff --git a/hypha/src/error.rs b/hypha/src/error.rs index e1948ba..124d406 100644 --- a/hypha/src/error.rs +++ b/hypha/src/error.rs @@ -5,6 +5,7 @@ use std::fmt::{Display, Formatter}; use std::num::ParseIntError; use std::path::StripPrefixError; use xenclient::XenClientError; +use xenstore::bus::XsdBusError; pub type Result = std::result::Result; @@ -98,3 +99,15 @@ impl From for HyphaError { HyphaError::new(value.to_string().as_str()) } } + +impl From for HyphaError { + fn from(value: uuid::Error) -> Self { + HyphaError::new(value.to_string().as_str()) + } +} + +impl From for HyphaError { + fn from(value: XsdBusError) -> Self { + HyphaError::new(value.to_string().as_str()) + } +} diff --git a/xencall/Cargo.toml b/xencall/Cargo.toml index 8020031..c91bcf5 100644 --- a/xencall/Cargo.toml +++ b/xencall/Cargo.toml @@ -20,7 +20,7 @@ version = "0.27.1" features = ["ioctl"] [dev-dependencies] -env_logger = "0.10.1" +env_logger = "0.11.0" [[example]] name = "xencall-domain-info" diff --git a/xenclient/examples/boot.rs b/xenclient/examples/boot.rs index dcbc15d..3819a1d 100644 --- a/xenclient/examples/boot.rs +++ b/xenclient/examples/boot.rs @@ -21,6 +21,7 @@ fn main() -> Result<(), XenClientError> { initrd_path: initrd_path.as_str(), cmdline: "debug elevator=noop", disks: vec![], + extra_keys: vec![], }; let domid = client.create(&config)?; println!("created domain {}", domid); diff --git a/xenclient/src/lib.rs b/xenclient/src/lib.rs index 9255de9..4966ede 100644 --- a/xenclient/src/lib.rs +++ b/xenclient/src/lib.rs @@ -7,10 +7,15 @@ mod x86; use crate::boot::BootSetup; use crate::elfloader::ElfImageLoader; use crate::x86::X86BootSetup; +use log::warn; use std::error::Error; use std::fmt::{Display, Formatter}; use std::fs::read; +use std::path::PathBuf; +use std::str::FromStr; use std::string::FromUtf8Error; +use std::thread; +use std::time::Duration; use uuid::Uuid; use xencall::sys::CreateDomain; use xencall::{XenCall, XenCallError}; @@ -21,7 +26,7 @@ use xenstore::client::{ }; pub struct XenClient { - store: XsdClient, + pub store: XsdClient, call: XenCall, } @@ -101,6 +106,7 @@ pub struct DomainConfig<'a> { pub initrd_path: &'a str, pub cmdline: &'a str, pub disks: Vec>, + pub extra_keys: Vec<(String, String)>, } impl XenClient { @@ -121,12 +127,101 @@ impl XenClient { Err(err) => { // ignore since destroying a domain is best // effort when an error occurs - let _ = self.call.destroy_domain(domid); + let _ = self.destroy(domid); Err(err) } } } + pub fn destroy(&mut self, domid: u32) -> Result<(), XenClientError> { + if let Err(err) = self.destroy_store(domid) { + warn!("failed to destroy store for domain {}: {}", domid, err); + } + self.call.destroy_domain(domid)?; + Ok(()) + } + + fn destroy_store(&mut self, domid: u32) -> Result<(), XenClientError> { + let dom_path = self.store.get_domain_path(domid)?; + let vm_path = self.store.read_string(&format!("{}/vm", dom_path))?; + if vm_path.is_empty() { + return Err(XenClientError::new( + "cannot destroy domain that doesn't exist", + )); + } + + let mut backend_paths: Vec = Vec::new(); + let console_frontend_path = format!("{}/console", dom_path); + let console_backend_path = self + .store + .read_string_optional(format!("{}/backend", console_frontend_path).as_str())?; + + for device_category in self + .store + .list_any(format!("{}/device", dom_path).as_str())? + { + for device_id in self + .store + .list_any(format!("{}/device/{}", dom_path, device_category).as_str())? + { + let device_path = format!("{}/device/{}/{}", dom_path, device_category, device_id); + let backend_path = self + .store + .read_string(format!("{}/backend", device_path).as_str())?; + backend_paths.push(backend_path); + } + } + + for backend in &backend_paths { + let state_path = format!("{}/state", backend); + let online_path = format!("{}/online", backend); + let mut tx = self.store.transaction()?; + let state = tx.read_string(&state_path)?; + if state.is_empty() { + break; + } + tx.write_string(&online_path, "0")?; + if !state.is_empty() && u32::from_str(&state).unwrap_or(0) != 6 { + tx.write_string(&state_path, "5")?; + } + tx.commit()?; + + let mut count: u32 = 0; + loop { + if count >= 100 { + return Err(XenClientError::new("unable to destroy device")); + } + let state = self.store.read_string(&state_path)?; + let state = i64::from_str(&state).unwrap_or(-1); + if state == 6 { + break; + } + thread::sleep(Duration::from_millis(100)); + count += 1; + } + } + + let mut tx = self.store.transaction()?; + let mut backend_removals: Vec = Vec::new(); + backend_removals.extend_from_slice(backend_paths.as_slice()); + if let Some(backend) = console_backend_path { + backend_removals.push(backend); + } + for path in &backend_removals { + let path = PathBuf::from(path); + let parent = path + .parent() + .ok_or(XenClientError::new("unable to get parent of backend path"))?; + tx.rm(parent + .to_str() + .ok_or(XenClientError::new("unable to convert parent to string"))?)?; + } + tx.rm(&vm_path)?; + tx.rm(&dom_path)?; + tx.commit()?; + Ok(()) + } + fn init( &mut self, domid: u32, @@ -205,6 +300,11 @@ impl XenClient { )?; tx.write_string(format!("{}/name", dom_path).as_str(), config.name)?; tx.write_string(format!("{}/name", vm_path).as_str(), config.name)?; + + for (key, value) in &config.extra_keys { + tx.write_string(format!("{}/{}", dom_path, key).as_str(), value)?; + } + tx.commit()?; } @@ -332,7 +432,7 @@ impl XenClient { ("physical-device-path", disk.block.path.to_string()), ( "physical-device", - format!("{:2x}:{:2x}", disk.block.major, disk.block.minor), + format!("{:02x}:{:02x}", disk.block.major, disk.block.minor), ), ]; diff --git a/xenstore/src/client.rs b/xenstore/src/client.rs index ad65693..1872836 100644 --- a/xenstore/src/client.rs +++ b/xenstore/src/client.rs @@ -49,6 +49,32 @@ pub trait XsdInterface { let result2 = self.set_perms(path, perms)?; Ok(result1 && result2) } + + fn read_string_optional(&mut self, path: &str) -> Result, XsdBusError> { + Ok(match self.read_string(path) { + Ok(value) => Some(value), + Err(error) => { + if error.to_string() == "ENOENT" { + None + } else { + return Err(error); + } + } + }) + } + + fn list_any(&mut self, path: &str) -> Result, XsdBusError> { + Ok(match self.list(path) { + Ok(value) => value, + Err(error) => { + if error.to_string() == "ENOENT" { + Vec::new() + } else { + return Err(error); + } + } + }) + } } impl XsdClient { @@ -225,7 +251,7 @@ impl XsdTransaction<'_> { pub fn end(&mut self, abort: bool) -> Result { let abort_str = if abort { "F" } else { "T" }; - trace!("transaction end abort={abort_str}"); + trace!("transaction end abort={}", abort); self.client .socket .send_single(self.tx, XSD_TRANSACTION_END, abort_str)?