diff --git a/hypha/bin/controller.rs b/hypha/bin/controller.rs index e87c947..da27b77 100644 --- a/hypha/bin/controller.rs +++ b/hypha/bin/controller.rs @@ -1,6 +1,7 @@ use clap::Parser; use hypha::ctl::Controller; use hypha::error::{HyphaError, Result}; +use std::path::PathBuf; #[derive(Parser, Debug)] #[command(version, about)] @@ -20,23 +21,28 @@ struct ControllerArgs { #[arg(short, long, default_value_t = 512)] mem: u64, - #[arg(short = 'C', long, default_value = "auto")] - cache: String, + #[arg(short, long, default_value = "auto")] + store: String, } fn main() -> Result<()> { env_logger::init(); let args = ControllerArgs::parse(); - let cache_path = if args.cache == "auto" { - default_cache_path() - .ok_or_else(|| HyphaError::new("unable to determine default cache path")) + let store_path = if args.store == "auto" { + default_store_path() + .ok_or_else(|| HyphaError::new("unable to determine default store path")) } else { - Ok(args.cache) + Ok(PathBuf::from(args.store)) }?; + let store_path = store_path + .to_str() + .map(|x| x.to_string()) + .ok_or_else(|| HyphaError::new("unable to convert store path to string"))?; + let mut controller = Controller::new( - cache_path, + store_path, args.kernel, args.initrd, args.image, @@ -48,9 +54,13 @@ fn main() -> Result<()> { Ok(()) } -fn default_cache_path() -> Option { +fn default_store_path() -> Option { let user_dirs = directories::UserDirs::new()?; let mut path = user_dirs.home_dir().to_path_buf(); - path.push(".hypha/cache"); - Some(path.to_str()?.to_string()) + if path == PathBuf::from("/root") { + path.push("/var/lib/hypha") + } else { + path.push(".hypha"); + } + Some(path) } diff --git a/hypha/src/ctl/mod.rs b/hypha/src/ctl/mod.rs index 072d024..83aa99c 100644 --- a/hypha/src/ctl/mod.rs +++ b/hypha/src/ctl/mod.rs @@ -1,11 +1,11 @@ -use crate::error::Result; +use crate::error::{HyphaError, Result}; use crate::image::cache::ImageCache; use crate::image::{ImageCompiler, ImageInfo}; use ocipkg::ImageName; use std::fs; use std::path::PathBuf; use uuid::Uuid; -use xenclient::{DomainConfig, XenClient}; +use xenclient::{DomainConfig, DomainDisk, XenClient}; pub struct Controller { image_cache: ImageCache, @@ -19,17 +19,18 @@ pub struct Controller { impl Controller { pub fn new( - cache_path: String, + store_path: String, kernel_path: String, initrd_path: String, image: String, vcpus: u32, mem: u64, ) -> Result { - fs::create_dir_all(&cache_path)?; + let mut image_cache_path = PathBuf::from(store_path); + image_cache_path.push("cache"); + fs::create_dir_all(&image_cache_path)?; let client = XenClient::open()?; - let mut image_cache_path = PathBuf::from(cache_path); image_cache_path.push("image"); fs::create_dir_all(&image_cache_path)?; let image_cache = ImageCache::new(&image_cache_path)?; @@ -53,14 +54,24 @@ impl Controller { pub fn launch(&mut self) -> Result { let uuid = Uuid::new_v4(); let name = format!("hypha-{uuid}"); - let _image_info = self.compile()?; + let image_info = self.compile()?; + let squashfs_path = image_info + .squashfs + .to_str() + .ok_or_else(|| HyphaError::new("failed to convert squashfs path to string"))?; let config = DomainConfig { + backend_domid: 0, name: &name, max_vcpus: self.vcpus, mem_mb: self.mem, kernel_path: self.kernel_path.as_str(), initrd_path: self.initrd_path.as_str(), - cmdline: "debug elevator=noop", + cmdline: "elevator=noop", + disks: vec![DomainDisk { + vdev: "xvda", + pdev: squashfs_path, + writable: false, + }], }; Ok(self.client.create(&config)?) } diff --git a/xenclient/examples/boot.rs b/xenclient/examples/boot.rs index ca750d4..dcbc15d 100644 --- a/xenclient/examples/boot.rs +++ b/xenclient/examples/boot.rs @@ -13,12 +13,14 @@ fn main() -> Result<(), XenClientError> { let initrd_path = args.get(2).expect("argument not specified"); let mut client = XenClient::open()?; let config = DomainConfig { + backend_domid: 0, name: "xenclient-test", max_vcpus: 1, mem_mb: 512, kernel_path: kernel_image_path.as_str(), initrd_path: initrd_path.as_str(), cmdline: "debug elevator=noop", + disks: vec![], }; let domid = client.create(&config)?; println!("created domain {}", domid); diff --git a/xenclient/src/lib.rs b/xenclient/src/lib.rs index 6490ba1..26e6a3f 100644 --- a/xenclient/src/lib.rs +++ b/xenclient/src/lib.rs @@ -16,7 +16,9 @@ use xencall::sys::CreateDomain; use xencall::{XenCall, XenCallError}; use xenevtchn::EventChannelError; use xenstore::bus::XsdBusError; -use xenstore::client::{XsPermissions, XsdClient, XsdInterface}; +use xenstore::client::{ + XsPermission, XsdClient, XsdInterface, XS_PERM_NONE, XS_PERM_READ, XS_PERM_READ_WRITE, +}; pub struct XenClient { store: XsdClient, @@ -78,13 +80,21 @@ impl From for XenClientError { } } +pub struct DomainDisk<'a> { + pub vdev: &'a str, + pub pdev: &'a str, + pub writable: bool, +} + pub struct DomainConfig<'a> { + pub backend_domid: u32, pub name: &'a str, pub max_vcpus: u32, pub mem_mb: u64, pub kernel_path: &'a str, pub initrd_path: &'a str, pub cmdline: &'a str, + pub disks: Vec>, } impl XenClient { @@ -100,55 +110,71 @@ impl XenClient { ..Default::default() }; let domid = self.call.create_domain(domain)?; + let backend_dom_path = self.store.get_domain_path(0)?; let dom_path = self.store.get_domain_path(domid)?; let uuid_string = Uuid::from_bytes(domain.handle).to_string(); let vm_path = format!("/vm/{}", uuid_string); - let libxl_path = format!("/libxl/{}", domid); - let ro_perm = XsPermissions { id: 0, perms: 0 }; - let rw_perm = XsPermissions { id: 0, perms: 0 }; - let no_perm = XsPermissions { id: 0, perms: 0 }; + let ro_perm = &[ + XsPermission { + id: 0, + perms: XS_PERM_NONE, + }, + XsPermission { + id: domid, + perms: XS_PERM_READ, + }, + ]; + + let rw_perm = &[XsPermission { + id: domid, + perms: XS_PERM_READ_WRITE, + }]; + + let no_perm = &[XsPermission { + id: 0, + perms: XS_PERM_NONE, + }]; { let mut tx = self.store.transaction()?; tx.rm(dom_path.as_str())?; - tx.mknod(dom_path.as_str(), &ro_perm)?; + tx.mknod(dom_path.as_str(), ro_perm)?; tx.rm(vm_path.as_str())?; - tx.mknod(vm_path.as_str(), &ro_perm)?; + tx.mknod(vm_path.as_str(), ro_perm)?; - tx.rm(libxl_path.as_str())?; - tx.mknod(vm_path.as_str(), &no_perm)?; - tx.mknod(format!("{}/device", vm_path).as_str(), &no_perm)?; + tx.mknod(vm_path.as_str(), no_perm)?; + tx.mknod(format!("{}/device", vm_path).as_str(), no_perm)?; tx.write_string(format!("{}/vm", dom_path).as_str(), &vm_path)?; - tx.mknod(format!("{}/cpu", dom_path).as_str(), &ro_perm)?; - tx.mknod(format!("{}/memory", dom_path).as_str(), &ro_perm)?; + tx.mknod(format!("{}/cpu", dom_path).as_str(), ro_perm)?; + tx.mknod(format!("{}/memory", dom_path).as_str(), ro_perm)?; - tx.mknod(format!("{}/control", dom_path).as_str(), &ro_perm)?; + tx.mknod(format!("{}/control", dom_path).as_str(), ro_perm)?; - tx.mknod(format!("{}/control/shutdown", dom_path).as_str(), &rw_perm)?; + tx.mknod(format!("{}/control/shutdown", dom_path).as_str(), rw_perm)?; tx.mknod( format!("{}/control/feature-poweroff", dom_path).as_str(), - &rw_perm, + rw_perm, )?; tx.mknod( format!("{}/control/feature-reboot", dom_path).as_str(), - &rw_perm, + rw_perm, )?; tx.mknod( format!("{}/control/feature-suspend", dom_path).as_str(), - &rw_perm, + rw_perm, )?; - tx.mknod(format!("{}/control/sysrq", dom_path).as_str(), &rw_perm)?; + tx.mknod(format!("{}/control/sysrq", dom_path).as_str(), rw_perm)?; - tx.mknod(format!("{}/data", dom_path).as_str(), &rw_perm)?; - tx.mknod(format!("{}/drivers", dom_path).as_str(), &rw_perm)?; - tx.mknod(format!("{}/feature", dom_path).as_str(), &rw_perm)?; - tx.mknod(format!("{}/attr", dom_path).as_str(), &rw_perm)?; - tx.mknod(format!("{}/error", dom_path).as_str(), &rw_perm)?; + tx.mknod(format!("{}/data", dom_path).as_str(), rw_perm)?; + tx.mknod(format!("{}/drivers", dom_path).as_str(), rw_perm)?; + tx.mknod(format!("{}/feature", dom_path).as_str(), rw_perm)?; + tx.mknod(format!("{}/attr", dom_path).as_str(), rw_perm)?; + tx.mknod(format!("{}/error", dom_path).as_str(), rw_perm)?; tx.write_string( format!("{}/uuid", vm_path).as_str(), @@ -156,7 +182,6 @@ impl XenClient { )?; tx.write_string(format!("{}/name", dom_path).as_str(), config.name)?; tx.write_string(format!("{}/name", vm_path).as_str(), config.name)?; - tx.write_string(format!("{}/type", libxl_path).as_str(), "pv")?; tx.commit()?; } @@ -222,53 +247,192 @@ impl XenClient { &xenstore_mfn.to_string(), )?; for i in 0..config.max_vcpus { - tx.write_string( - format!("{}/cpu/{}/availability", dom_path, i).as_str(), - "online", - )?; + let path = format!("{}/cpu/{}", dom_path, i); + tx.mkdir(&path)?; + tx.set_perms(&path, ro_perm)?; + let path = format!("{}/cpu/{}/availability", dom_path, i); + tx.write_string(&path, "online")?; + tx.set_perms(&path, ro_perm)?; } tx.commit()?; } - - self.console_device_add(&dom_path.to_string(), domid, console_evtchn, console_mfn)?; - self.store - .introduce_domain(domid, xenstore_mfn, xenstore_evtchn)?; + if !self + .store + .introduce_domain(domid, xenstore_mfn, xenstore_evtchn)? + { + return Err(XenClientError::new("failed to introduce domain")); + } + self.console_device_add( + &dom_path, + &backend_dom_path, + config.backend_domid, + domid, + console_evtchn, + console_mfn, + )?; + for (index, disk) in config.disks.iter().enumerate() { + self.disk_device_add( + &dom_path, + &backend_dom_path, + config.backend_domid, + domid, + index, + disk, + )?; + } self.call.unpause_domain(domid)?; Ok(domid) } + fn disk_device_add( + &mut self, + dom_path: &str, + backend_dom_path: &str, + backend_domid: u32, + domid: u32, + index: usize, + disk: &DomainDisk, + ) -> Result<(), XenClientError> { + let id = (202 << 8) | (index << 4) as u64; + let backend_items: Vec<(&str, String)> = vec![ + ("frontend-id", domid.to_string()), + ("params", disk.pdev.to_string()), + ("script", "/etc/xen/scripts/block".to_string()), + ("online", "1".to_string()), + ("removable", "0".to_string()), + ("bootable", "1".to_string()), + ("state", "1".to_string()), + ("dev", disk.vdev.to_string()), + ("type", "phy".to_string()), + ("mode", if disk.writable { "w" } else { "r" }.to_string()), + ("device-type", "disk".to_string()), + ("discard-enable", "0".to_string()), + ("specification", "xen".to_string()), + ]; + + let frontend_items: Vec<(&str, String)> = vec![ + ("backend-id", backend_domid.to_string()), + ("state", "1".to_string()), + ("virtual-device", id.to_string()), + ("device-type", "disk".to_string()), + ("trusted", "1".to_string()), + ("protocol", "x86_64-abi".to_string()), + ]; + + self.device_add( + "vbd", + id, + dom_path, + backend_dom_path, + backend_domid, + domid, + frontend_items, + backend_items, + )?; + Ok(()) + } + fn console_device_add( &mut self, - dom_path: &String, + dom_path: &str, + backend_dom_path: &str, + backend_domid: u32, domid: u32, port: u32, mfn: u64, ) -> Result<(), XenClientError> { - let frontend_path = format!("{}/console", dom_path); - let backend_path = format!("{}/backend/console/{}/{}", dom_path, domid, 0); - let mut tx = self.store.transaction()?; - tx.write_string( - format!("{}/frontend-id", backend_path).as_str(), - &domid.to_string(), - )?; - tx.write_string(format!("{}/online", backend_path).as_str(), "1")?; - tx.write_string(format!("{}/state", backend_path).as_str(), "1")?; - tx.write_string(format!("{}/protocol", backend_path).as_str(), "vt100")?; + let backend_entries = vec![ + ("frontend-id", domid.to_string()), + ("online", "1".to_string()), + ("state", "1".to_string()), + ("protocol", "vt100".to_string()), + ]; - tx.write_string(format!("{}/backend-id", frontend_path).as_str(), "0")?; - tx.write_string(format!("{}/limit", frontend_path).as_str(), "1048576")?; - tx.write_string(format!("{}/type", frontend_path).as_str(), "xenconsoled")?; - tx.write_string(format!("{}/output", frontend_path).as_str(), "pty")?; - tx.write_string(format!("{}/tty", frontend_path).as_str(), "")?; - tx.write_string( - format!("{}/port", frontend_path).as_str(), - &port.to_string(), - )?; - tx.write_string( - format!("{}/ring-ref", frontend_path).as_str(), - &mfn.to_string(), + let frontend_entries = vec![ + ("backend-id", backend_domid.to_string()), + ("limit", "1048576".to_string()), + ("type", "xenconsoled".to_string()), + ("output", "pty".to_string()), + ("tty", "".to_string()), + ("port", port.to_string()), + ("ring-ref", mfn.to_string()), + ]; + + self.device_add( + "console", + 0, + dom_path, + backend_dom_path, + backend_domid, + domid, + frontend_entries, + backend_entries, )?; + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn device_add( + &mut self, + typ: &str, + id: u64, + dom_path: &str, + backend_dom_path: &str, + backend_domid: u32, + domid: u32, + frontend_items: Vec<(&str, String)>, + backend_items: Vec<(&str, String)>, + ) -> Result<(), XenClientError> { + let console_zero = typ == "console" && id == 0; + + let frontend_path = if console_zero { + format!("{}/console", dom_path) + } else { + format!("{}/device/{}/{}", dom_path, typ, id) + }; + let backend_path = format!("{}/backend/{}/{}/{}", backend_dom_path, typ, domid, id); + + let mut backend_items: Vec<(&str, String)> = backend_items.clone(); + let mut frontend_items: Vec<(&str, String)> = frontend_items.clone(); + backend_items.push(("frontend", frontend_path.clone())); + frontend_items.push(("backend", backend_path.clone())); + let frontend_perms = &[ + XsPermission { + id: domid, + perms: XS_PERM_NONE, + }, + XsPermission { + id: backend_domid, + perms: XS_PERM_READ, + }, + ]; + + let backend_perms = &[ + XsPermission { + id: backend_domid, + perms: XS_PERM_NONE, + }, + XsPermission { + id: domid, + perms: XS_PERM_READ, + }, + ]; + + let mut tx = self.store.transaction()?; + tx.mknod(&frontend_path, frontend_perms)?; + for (p, value) in &frontend_items { + let path = format!("{}/{}", frontend_path, *p); + tx.write_string(&path, value)?; + if !console_zero { + tx.set_perms(&path, frontend_perms)?; + } + } + tx.mknod(&backend_path, backend_perms)?; + for (p, value) in &backend_items { + let path = format!("{}/{}", backend_path, *p); + tx.write_string(&path, value)?; + } tx.commit()?; Ok(()) } diff --git a/xenstore/src/bus.rs b/xenstore/src/bus.rs index 9c22b20..d30fb0b 100644 --- a/xenstore/src/bus.rs +++ b/xenstore/src/bus.rs @@ -120,13 +120,7 @@ impl XsdResponse { } pub fn parse_bool(&self) -> Result { - if self.payload.is_empty() { - Err(XsdBusError::new( - "Expected bool payload to be at least one byte.", - )) - } else { - Ok(self.payload[0] == 0) - } + Ok(true) } } diff --git a/xenstore/src/client.rs b/xenstore/src/client.rs index 90aa9c7..9c4344e 100644 --- a/xenstore/src/client.rs +++ b/xenstore/src/client.rs @@ -1,19 +1,39 @@ use crate::bus::{XsdBusError, XsdSocket}; use crate::sys::{ - XSD_DIRECTORY, XSD_GET_DOMAIN_PATH, XSD_INTRODUCE, XSD_MKDIR, XSD_READ, XSD_RM, + XSD_DIRECTORY, XSD_GET_DOMAIN_PATH, XSD_INTRODUCE, XSD_MKDIR, XSD_READ, XSD_RM, XSD_SET_PERMS, XSD_TRANSACTION_END, XSD_TRANSACTION_START, XSD_WRITE, }; +use log::trace; use std::ffi::CString; +pub const XS_PERM_NONE: u32 = 0x00; +pub const XS_PERM_READ: u32 = 0x01; +pub const XS_PERM_WRITE: u32 = 0x02; +pub const XS_PERM_READ_WRITE: u32 = XS_PERM_READ | XS_PERM_WRITE; + pub struct XsdClient { pub socket: XsdSocket, } -pub struct XsPermissions { +#[derive(Debug, Copy, Clone)] +pub struct XsPermission { pub id: u32, pub perms: u32, } +impl XsPermission { + pub fn encode(&self) -> Result { + let c = match self.perms { + XS_PERM_READ_WRITE => 'b', + XS_PERM_WRITE => 'w', + XS_PERM_READ => 'r', + XS_PERM_NONE => 'n', + _ => return Err(XsdBusError::new("invalid permissions")), + }; + Ok(format!("{}{}", c, self.id)) + } +} + pub trait XsdInterface { fn list(&mut self, path: &str) -> Result, XsdBusError>; fn read(&mut self, path: &str) -> Result, XsdBusError>; @@ -22,9 +42,12 @@ pub trait XsdInterface { fn write_string(&mut self, path: &str, data: &str) -> Result; fn mkdir(&mut self, path: &str) -> Result; fn rm(&mut self, path: &str) -> Result; + fn set_perms(&mut self, path: &str, perms: &[XsPermission]) -> Result; - fn mknod(&mut self, path: &str, _perm: &XsPermissions) -> Result { - self.write_string(path, "") + fn mknod(&mut self, path: &str, perms: &[XsPermission]) -> Result { + let result1 = self.write_string(path, "")?; + let result2 = self.set_perms(path, perms)?; + Ok(result1 && result2) } } @@ -35,16 +58,19 @@ impl XsdClient { } fn list(&mut self, tx: u32, path: &str) -> Result, XsdBusError> { + trace!("list tx={tx} path={path}"); let response = self.socket.send_single(tx, XSD_DIRECTORY, path)?; response.parse_string_vec() } fn read(&mut self, tx: u32, path: &str) -> Result, XsdBusError> { + trace!("read tx={tx} path={path}"); let response = self.socket.send_single(tx, XSD_READ, path)?; Ok(response.payload) } fn write(&mut self, tx: u32, path: &str, data: Vec) -> Result { + trace!("write tx={tx} path={path} data={:?}", data); let mut buffer = Vec::new(); let path = CString::new(path)?; buffer.extend_from_slice(path.as_bytes_with_nul()); @@ -54,14 +80,34 @@ impl XsdClient { } fn mkdir(&mut self, tx: u32, path: &str) -> Result { + trace!("mkdir tx={tx} path={path}"); self.socket.send_single(tx, XSD_MKDIR, path)?.parse_bool() } fn rm(&mut self, tx: u32, path: &str) -> Result { + trace!("rm tx={tx} path={path}"); self.socket.send_single(tx, XSD_RM, path)?.parse_bool() } + fn set_perms( + &mut self, + tx: u32, + path: &str, + perms: &[XsPermission], + ) -> Result { + trace!("set_perms tx={tx} path={path} perms={:?}", perms); + let mut items: Vec = Vec::new(); + items.push(path.to_string()); + for perm in perms { + items.push(perm.encode()?); + } + let items_str: Vec<&str> = items.iter().map(|x| x.as_str()).collect(); + let response = self.socket.send_multiple(tx, XSD_SET_PERMS, &items_str)?; + response.parse_bool() + } + pub fn transaction(&mut self) -> Result { + trace!("transaction start"); let response = self.socket.send_single(0, XSD_TRANSACTION_START, "")?; let str = response.parse_string()?; let tx = str.parse::()?; @@ -79,18 +125,19 @@ impl XsdClient { &mut self, domid: u32, mfn: u64, - eventchn: u32, - ) -> Result { + evtchn: u32, + ) -> Result { + trace!("introduce domain domid={domid} mfn={mfn} evtchn={evtchn}"); let response = self.socket.send_multiple( 0, XSD_INTRODUCE, &[ domid.to_string().as_str(), mfn.to_string().as_str(), - eventchn.to_string().as_str(), + evtchn.to_string().as_str(), ], )?; - response.parse_string() + response.parse_bool() } } @@ -127,6 +174,10 @@ impl XsdInterface for XsdClient { fn rm(&mut self, path: &str) -> Result { self.rm(0, path) } + + fn set_perms(&mut self, path: &str, perms: &[XsPermission]) -> Result { + self.set_perms(0, path, perms) + } } impl XsdInterface for XsdTransaction<'_> { @@ -157,12 +208,17 @@ impl XsdInterface for XsdTransaction<'_> { fn rm(&mut self, path: &str) -> Result { self.client.rm(self.tx, path) } + + fn set_perms(&mut self, path: &str, perms: &[XsPermission]) -> Result { + self.client.set_perms(self.tx, path, perms) + } } impl XsdTransaction<'_> { pub fn end(&mut self, abort: bool) -> Result { let abort_str = if abort { "F" } else { "T" }; + trace!("transaction end abort={abort_str}"); self.client .socket .send_single(self.tx, XSD_TRANSACTION_END, abort_str)?