release when??

main
eater 3 years ago
parent b5c4d38b77
commit b538ff1657
Signed by: eater
GPG Key ID: AD2560A0F84F0759

@ -5,7 +5,6 @@ features = [
"uefi", "uefi",
"spice", "spice",
"pulse", "pulse",
# "scream",
"looking-glass" "looking-glass"
] ]
@ -43,4 +42,4 @@ addr = "0b:00.3"
[looking-glass] [looking-glass]
width = 2560 width = 2560
height = 1080 height = 1080

@ -1,3 +1,6 @@
[vore]
group = "vore"
[qemu] [qemu]
script = "qemu.lua" script = "qemu.lua"

@ -126,7 +126,7 @@ vore:set_build_command(function(instance, vm)
end end
if instance.pulse.enabled then if instance.pulse.enabled then
vm:arg("-device", "intel-hda", "-device", "hda-duplex") vm:arg("-device", "intel-hda", "-device", "hda-duplex,audiodev=pa0")
vm:arg("-audiodev", "pa,server=/run/user/1000/pulse/native,id=pa0") vm:arg("-audiodev", "pa,server=/run/user/1000/pulse/native,id=pa0")
end end

@ -4,7 +4,10 @@ version = "0.1.0"
authors = ["eater <=@eater.me>"] authors = ["eater <=@eater.me>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features]
default = ["client"]
host = ["qapi", "qapi-qmp", "mlua"]
client = []
[dependencies] [dependencies]
config = { version = "0.11.0", default-features = false, features = ["toml"] } config = { version = "0.11.0", default-features = false, features = ["toml"] }
@ -13,10 +16,10 @@ serde_json = "1.0.64"
toml = "*" toml = "*"
anyhow = "1.0.40" anyhow = "1.0.40"
kiam = "0.1" kiam = "0.1"
mlua = { version = "0.5.3", features = ["lua54", "serialize", "send"] } mlua = { optional = true, version = "0.5.3", features = ["lua54", "serialize", "send"] }
beau_collector = "0.2.1" beau_collector = "0.2.1"
qapi-qmp = "0.7.0" qapi-qmp = { optional = true, version = "0.7.0" }
qapi = { version = "0.7.0", features = ["qapi-qmp"] } qapi = { optional = true, version = "0.7.0", features = ["qapi-qmp"] }
libc = "0.2.94" libc = "0.2.94"
lazy_static = "1.4.0" lazy_static = "1.4.0"
paste = "1.0" paste = "1.0"

@ -0,0 +1,21 @@
// File with all static constants like e.g. paths
#![allow(clippy::manual_unwrap_or)]
macro_rules! default_env {
($val:expr, $def:expr) => {
match option_env!($val) {
None => $def,
Some(x) => x,
};
};
}
pub const VORE_DIRECTORY: &str = default_env!("VORE_DIRECTORY", "/var/lib/vore");
pub const VORE_SOCKET: &str = default_env!("VORE_SOCKET", "/run/vore.sock");
#[cfg(debug_assertions)]
pub const VORE_CONFIG: &str = default_env!(
"VORE_CONFIG",
concat!(file!(), "/../../../../config/vored.toml")
);
#[cfg(not(debug_assertions))]
pub const VORE_CONFIG: &str = default_env!("VORE_CONFIG", "/etc/vore/vored.toml");

@ -19,7 +19,7 @@ lazy_static! {
pub fn get_cpus() -> Vec<Cpu> { pub fn get_cpus() -> Vec<Cpu> {
if cfg!(target_os = "linux") { if cfg!(target_os = "linux") {
return crate::cpu_list::linux::get_cpus(); crate::cpu_list::linux::get_cpus()
} else { } else {
unimplemented!(); unimplemented!();
} }
@ -32,7 +32,7 @@ pub struct CpuList {
impl CpuList { impl CpuList {
pub fn _get() -> CpuList { pub fn _get() -> CpuList {
return *CPU_LIST; *CPU_LIST
} }
pub fn _amount() -> usize { pub fn _amount() -> usize {

@ -1,15 +1,69 @@
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::CString;
pub const GLOBAL_CONFIG_LOCATION: &str = "/home/eater/projects/vored/config/global.toml"; use std::fs;
use std::fs::Permissions;
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GlobalConfig { pub struct GlobalConfig {
pub vore: GlobalVoreConfig,
pub qemu: GlobalQemuConfig, pub qemu: GlobalQemuConfig,
pub uefi: HashMap<String, GlobalUefiConfig>, pub uefi: HashMap<String, GlobalUefiConfig>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
pub struct GlobalVoreConfig {
#[serde(default)]
pub group: Option<String>,
#[serde(default)]
pub unix_group_id: Option<libc::gid_t>,
}
impl GlobalVoreConfig {
pub fn get_gid(&mut self) -> Result<Option<u32>, anyhow::Error> {
if let Some(id) = self.unix_group_id {
return Ok(Some(id));
}
let name = self.group.as_ref().cloned();
name.map(|group_name| {
let group_name_c = CString::new(group_name.as_str())?;
Ok(unsafe {
let group = libc::getgrnam(group_name_c.as_ptr());
if group.is_null() {
anyhow::bail!("No group found with the name '{}'", group_name);
}
let gid = (*group).gr_gid;
self.unix_group_id = Some(gid);
gid
})
})
.transpose()
}
pub fn chown(&mut self, path: &str) -> Result<(), anyhow::Error> {
if let Some(gid) = self.get_gid()? {
let meta = fs::metadata(path)?;
let path_c = CString::new(path)?;
unsafe {
libc::chown(path_c.as_ptr(), meta.uid(), gid);
}
fs::set_permissions(path, Permissions::from_mode(0o774))?;
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GlobalQemuConfig { pub struct GlobalQemuConfig {
pub script: String, pub script: String,

@ -1,6 +1,6 @@
use anyhow::{Context, Error}; use anyhow::{Context, Error};
use config::{Config, File, FileFormat, Value}; use config::{Config, File, FileFormat, Value};
use serde::de::{Visitor}; use serde::de::Visitor;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
@ -12,6 +12,7 @@ pub struct InstanceConfig {
pub arch: String, pub arch: String,
pub chipset: String, pub chipset: String,
pub kvm: bool, pub kvm: bool,
pub auto_start: bool,
pub memory: u64, pub memory: u64,
pub cpu: CpuConfig, pub cpu: CpuConfig,
pub disks: Vec<DiskConfig>, pub disks: Vec<DiskConfig>,
@ -46,6 +47,10 @@ impl InstanceConfig {
instance_config.memory = parse_size(&mem)?; instance_config.memory = parse_size(&mem)?;
} }
if let Ok(auto_start) = config.get_bool("machine.auto-start") {
instance_config.auto_start = auto_start;
}
if let Ok(cpu) = config.get_table("cpu") { if let Ok(cpu) = config.get_table("cpu") {
instance_config.cpu.apply_table(cpu)? instance_config.cpu.apply_table(cpu)?
} }
@ -74,16 +79,15 @@ impl InstanceConfig {
} }
} }
instance_config.looking_glass = LookingGlassConfig::from_table( instance_config.looking_glass =
config.get_table("looking-glass").unwrap_or_default(), LookingGlassConfig::from_table(config.get_table("looking-glass").unwrap_or_default())?;
)?; instance_config.scream =
instance_config.scream = ScreamConfig::from_table( ScreamConfig::from_table(config.get_table("scream").unwrap_or_default())?;
config.get_table("scream").unwrap_or_default(),
)?;
instance_config.spice = instance_config.spice =
SpiceConfig::from_table(config.get_table("spice").unwrap_or_default())?; SpiceConfig::from_table(config.get_table("spice").unwrap_or_default())?;
instance_config.pulse = PulseConfig::from_table(config.get_table("pulse").unwrap_or_default())?; instance_config.pulse =
PulseConfig::from_table(config.get_table("pulse").unwrap_or_default())?;
if let Ok(features) = config.get::<Vec<String>>("machine.features") { if let Ok(features) = config.get::<Vec<String>>("machine.features") {
for feature in features { for feature in features {
@ -109,6 +113,7 @@ impl Default for InstanceConfig {
arch: std::env::consts::ARCH.to_string(), arch: std::env::consts::ARCH.to_string(),
chipset: "q35".to_string(), chipset: "q35".to_string(),
kvm: true, kvm: true,
auto_start: false,
// 2 GB // 2 GB
memory: 2 * 1024 * 1024 * 1024, memory: 2 * 1024 * 1024 * 1024,
cpu: Default::default(), cpu: Default::default(),
@ -191,23 +196,19 @@ impl CpuConfig {
if !table.contains_key("amount") { if !table.contains_key("amount") {
self.amount = self.sockets * self.dies * self.cores * self.threads; self.amount = self.sockets * self.dies * self.cores * self.threads;
} else { } else if table
if table .keys()
.keys() .any(|x| ["cores", "sockets", "dies", "threads"].contains(&x.as_str()))
.any(|x| ["cores", "sockets", "dies", "threads"].contains(&x.as_str())) {
{ let calc_amount = self.sockets * self.dies * self.cores * self.threads;
let calc_amount = self.sockets * self.dies * self.cores * self.threads; if self.amount != calc_amount {
if self.amount != calc_amount { return Err(anyhow::Error::msg(format!("Amount of cpu's ({}) from sockets ({}), dies ({}), cores ({}) and threads ({}) differs from specified ({}) cpu's", calc_amount, self.sockets, self.dies, self.cores, self.threads, self.amount)));
Err(anyhow::Error::msg(format!("Amount of cpu's ({}) from sockets ({}), dies ({}), cores ({}) and threads ({}) differs from specified ({}) cpu's", calc_amount, self.sockets, self.dies, self.cores, self.threads, self.amount)))?;
}
} else {
if (self.amount % 2) == 0 {
self.cores = self.amount / 2;
} else {
self.threads = 1;
self.cores = self.amount;
}
} }
} else if (self.amount % 2) == 0 {
self.cores = self.amount / 2;
} else {
self.threads = 1;
self.cores = self.amount;
} }
Ok(()) Ok(())
@ -240,7 +241,7 @@ fn parse_size(orig_input: &str) -> Result<u64, anyhow::Error> {
input = &input[..input.len() - 1]; input = &input[..input.len() - 1];
} }
if input.len() == 0 { if input.is_empty() {
return Err(anyhow::Error::msg(format!( return Err(anyhow::Error::msg(format!(
"'{}' is not a valid size", "'{}' is not a valid size",
orig_input orig_input
@ -286,9 +287,7 @@ pub struct ScreamConfig {
} }
impl ScreamConfig { impl ScreamConfig {
pub fn from_table( pub fn from_table(table: HashMap<String, Value>) -> Result<ScreamConfig, anyhow::Error> {
table: HashMap<String, Value>,
) -> Result<ScreamConfig, anyhow::Error> {
let mut cfg = ScreamConfig::default(); let mut cfg = ScreamConfig::default();
if let Some(enabled) = table.get("enabled").cloned() { if let Some(enabled) = table.get("enabled").cloned() {
cfg.enabled = enabled.into_bool()?; cfg.enabled = enabled.into_bool()?;
@ -368,9 +367,7 @@ impl LookingGlassConfig {
self.buffer_size = buffer_size; self.buffer_size = buffer_size;
} }
pub fn from_table( pub fn from_table(table: HashMap<String, Value>) -> Result<LookingGlassConfig, anyhow::Error> {
table: HashMap<String, Value>,
) -> Result<LookingGlassConfig, anyhow::Error> {
let mut cfg = LookingGlassConfig::default(); let mut cfg = LookingGlassConfig::default();
if let Some(enabled) = table.get("enabled").cloned() { if let Some(enabled) = table.get("enabled").cloned() {
@ -434,12 +431,14 @@ impl DiskConfig {
}).to_string() }).to_string()
}; };
let preset = table.get("preset") let preset = table
.get("preset")
.cloned() .cloned()
.context("Every disk should have a preset set")? .context("Every disk should have a preset set")?
.into_str()?; .into_str()?;
let read_only = table.get("read-only") let read_only = table
.get("read-only")
.cloned() .cloned()
.map(|x| x.into_bool()) .map(|x| x.into_bool())
.transpose() .transpose()
@ -459,15 +458,16 @@ impl DiskConfig {
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
pub struct VfioConfig { pub struct VfioConfig {
pub address: PCIAddress, pub address: PciAddress,
pub vendor: Option<u32>, pub vendor: Option<u32>,
pub device: Option<u32>, pub device: Option<u32>,
pub index: u32, pub index: u32,
pub graphics: bool, pub graphics: bool,
pub multifunction: bool, pub multifunction: bool,
pub reserve: bool,
} }
pub fn read_pci_ids(addr: &PCIAddress) -> Result<(u32, u32), anyhow::Error> { pub fn read_pci_ids(addr: &PciAddress) -> Result<(u32, u32), anyhow::Error> {
let device = std::fs::read_to_string(format!("/sys/bus/pci/devices/{:#}/device", addr)) let device = std::fs::read_to_string(format!("/sys/bus/pci/devices/{:#}/device", addr))
.with_context(|| { .with_context(|| {
format!( format!(
@ -497,7 +497,7 @@ impl VfioConfig {
.get("addr") .get("addr")
.or_else(|| table.get("address")) .or_else(|| table.get("address"))
.cloned() .cloned()
.map(|x| PCIAddress::from_str(&x.into_str()?)) .map(|x| PciAddress::from_str(&x.into_str()?))
.transpose()?; .transpose()?;
let vendor = table let vendor = table
@ -548,7 +548,7 @@ impl VfioConfig {
(None, Some(vendor), Some(device)) => { (None, Some(vendor), Some(device)) => {
let mut counter = index; let mut counter = index;
let mut items: Vec<(PCIAddress, u32, u32)> = vec![]; let mut items: Vec<(PciAddress, u32, u32)> = vec![];
for entry in std::fs::read_dir("/sys/bus/pci/devices")? { for entry in std::fs::read_dir("/sys/bus/pci/devices")? {
let entry = entry?; let entry = entry?;
@ -556,7 +556,7 @@ impl VfioConfig {
let addr_name = file_name let addr_name = file_name
.to_str() .to_str()
.ok_or_else(|| anyhow::anyhow!("Failed to parse PCI device name"))?; .ok_or_else(|| anyhow::anyhow!("Failed to parse PCI device name"))?;
let addr = PCIAddress::from_str(addr_name)?; let addr = PciAddress::from_str(addr_name)?;
let (found_vendor, found_device) = read_pci_ids(&addr)?; let (found_vendor, found_device) = read_pci_ids(&addr)?;
items.push((addr, found_vendor, found_device)); items.push((addr, found_vendor, found_device));
} }
@ -596,6 +596,7 @@ impl VfioConfig {
index: 0, index: 0,
graphics: false, graphics: false,
multifunction: false, multifunction: false,
reserve: false,
}; };
if let Some(graphics) = table.get("graphics").cloned() { if let Some(graphics) = table.get("graphics").cloned() {
@ -606,6 +607,10 @@ impl VfioConfig {
cfg.multifunction = multifunction.into_bool()?; cfg.multifunction = multifunction.into_bool()?;
} }
if let Some(reserve) = table.get("reserve").cloned() {
cfg.reserve = reserve.into_bool()?;
}
Ok(cfg) Ok(cfg)
} }
} }
@ -617,9 +622,7 @@ pub struct PulseConfig {
impl PulseConfig { impl PulseConfig {
pub fn from_table(table: HashMap<String, Value>) -> Result<PulseConfig, anyhow::Error> { pub fn from_table(table: HashMap<String, Value>) -> Result<PulseConfig, anyhow::Error> {
let mut cfg = PulseConfig { let mut cfg = PulseConfig { enabled: false };
enabled: false,
};
if let Some(enabled) = table.get("enabled").cloned() { if let Some(enabled) = table.get("enabled").cloned() {
cfg.enabled = enabled.into_bool()?; cfg.enabled = enabled.into_bool()?;
@ -629,7 +632,6 @@ impl PulseConfig {
} }
} }
#[derive(Deserialize, Serialize, Clone, Debug, Default)] #[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct SpiceConfig { pub struct SpiceConfig {
pub enabled: bool, pub enabled: bool,
@ -656,17 +658,17 @@ impl SpiceConfig {
} }
#[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] #[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct PCIAddress { pub struct PciAddress {
domain: u32, domain: u32,
bus: u8, bus: u8,
slot: u8, slot: u8,
func: u8, func: u8,
} }
impl<'de> Deserialize<'de> for PCIAddress { impl<'de> Deserialize<'de> for PciAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
struct X; struct X;
impl Visitor<'_> for X { impl Visitor<'_> for X {
@ -676,8 +678,10 @@ impl<'de> Deserialize<'de> for PCIAddress {
formatter.write_str("Expecting a string") formatter.write_str("Expecting a string")
} }
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
E: de::Error, { where
E: de::Error,
{
Ok(v.to_string()) Ok(v.to_string())
} }
@ -687,21 +691,22 @@ impl<'de> Deserialize<'de> for PCIAddress {
} }
let x = deserializer.deserialize_string(X)?; let x = deserializer.deserialize_string(X)?;
Ok(PCIAddress::from_str(&x).map_err(|x| de::Error::custom(x))?)
PciAddress::from_str(&x).map_err(de::Error::custom)
} }
} }
impl Serialize for PCIAddress { impl Serialize for PciAddress {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where where
S: Serializer, S: Serializer,
{ {
serializer.serialize_str(&self.to_string()) serializer.serialize_str(&self.to_pci_string())
} }
} }
impl PCIAddress { impl PciAddress {
fn to_string(&self) -> String { fn to_pci_string(&self) -> String {
format!( format!(
"{:04x}:{:02x}:{:02x}.{:x}", "{:04x}:{:02x}:{:02x}.{:x}",
self.domain, self.bus, self.slot, self.func self.domain, self.bus, self.slot, self.func
@ -709,7 +714,7 @@ impl PCIAddress {
} }
} }
impl Debug for PCIAddress { impl Debug for PciAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("PCIAddress(")?; f.write_str("PCIAddress(")?;
if f.alternate() && self.domain == 0 { if f.alternate() && self.domain == 0 {
@ -724,7 +729,7 @@ impl Debug for PCIAddress {
} }
} }
impl Display for PCIAddress { impl Display for PciAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if f.alternate() && self.domain == 0 { if f.alternate() && self.domain == 0 {
f.write_str(&format!("{:04x}:", self.domain))?; f.write_str(&format!("{:04x}:", self.domain))?;
@ -737,15 +742,15 @@ impl Display for PCIAddress {
} }
} }
impl FromStr for PCIAddress { impl FromStr for PciAddress {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut rev = s.rsplit(":"); let mut rev = s.rsplit(':');
let mut addr = PCIAddress::default(); let mut addr = PciAddress::default();
if let Some(slot_and_func) = rev.next() { if let Some(slot_and_func) = rev.next() {
let mut splitter = slot_and_func.split("."); let mut splitter = slot_and_func.split('.');
if let Some(slot) = splitter.next() { if let Some(slot) = splitter.next() {
addr.slot = u8::from_str_radix(slot, 16)?; addr.slot = u8::from_str_radix(slot, 16)?;
@ -770,20 +775,20 @@ impl FromStr for PCIAddress {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::PCIAddress; use crate::PciAddress;
use std::str::FromStr; use std::str::FromStr;
#[test] #[test]
fn test_input_and_output_are_same() { fn test_input_and_output_are_same() {
assert_eq!( assert_eq!(
PCIAddress::from_str("0000:00:00.1") PciAddress::from_str("0000:00:00.1")
.expect("Failed to parse correct string") .expect("Failed to parse correct string")
.to_string(), .to_string(),
"0000:00:00.1" "0000:00:00.1"
); );
assert_eq!( assert_eq!(
PCIAddress::from_str("0000:00:01.0") PciAddress::from_str("0000:00:01.0")
.expect("Failed to parse correct string") .expect("Failed to parse correct string")
.to_string(), .to_string(),
"0000:00:01.0" "0000:00:01.0"

@ -4,18 +4,22 @@ mod qemu;
mod virtual_machine; mod virtual_machine;
mod cpu_list; mod cpu_list;
pub mod rpc; pub mod rpc;
pub mod consts;
mod virtual_machine_info;
pub use global_config::*; pub use global_config::*;
pub use instance_config::*; pub use instance_config::*;
pub use qemu::QemuCommandBuilder; pub use qemu::QemuCommandBuilder;
#[cfg(feature = "host")]
pub use virtual_machine::*; pub use virtual_machine::*;
use log::LevelFilter; pub use virtual_machine_info::*;
pub fn init_logging() { pub fn init_logging() {
let mut builder = pretty_env_logger::formatted_timed_builder(); let mut builder = pretty_env_logger::formatted_timed_builder();
#[cfg(debug_assertions)] { #[cfg(debug_assertions)] {
use log::LevelFilter;
builder.filter_level(LevelFilter::Debug); builder.filter_level(LevelFilter::Debug);
} }
builder.parse_filters(&std::env::var("RUST_LOG").unwrap_or("".to_string())); builder.parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| "".to_string()));
builder.init(); builder.init();
} }

@ -1,4 +1,7 @@
use crate::{GlobalConfig, InstanceConfig, GLOBAL_CONFIG_LOCATION}; #![cfg(feature = "host")]
use crate::consts::VORE_CONFIG;
use crate::{GlobalConfig, InstanceConfig};
use anyhow::Context; use anyhow::Context;
use mlua::prelude::LuaError; use mlua::prelude::LuaError;
use mlua::{ use mlua::{
@ -8,31 +11,30 @@ use mlua::{
use serde::ser::Error; use serde::ser::Error;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{PathBuf, Path};
use std::sync::{Arc, Mutex, Weak};
use std::fs; use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
#[derive(Debug, Default, Deserialize, Clone)] #[derive(Debug, Default, Deserialize, Clone)]
struct VM { struct VirtualMachine {
args: Vec<String>, args: Vec<String>,
bus_ids: HashMap<String, usize>, bus_ids: HashMap<String, usize>,
devices: HashMap<String, String>, devices: HashMap<String, String>,
device: bool, device: bool,
} }
impl UserData for VM { impl UserData for VirtualMachine {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method_mut("arg", |_, this, args: MultiValue| { methods.add_method_mut("arg", |_, this, args: MultiValue| {
for item in args.iter() { for item in args.iter() {
if let Value::String(item) = item { if let Value::String(item) = item {
let item = item.to_str()?.to_string(); let item = item.to_str()?.to_string();
if this.device { if this.device {
let mut items = item.split(","); let mut items = item.split(',');
if let Some(_type) = items.next() { if let Some(_type) = items.next() {
for item in items { for item in items {
if item.starts_with("id=") { if let Some(id) = item.strip_prefix("id=") {
this.devices this.devices.insert(_type.to_string(), id.to_string());
.insert(_type.to_string(), item[3..].to_string());
break; break;
} }
} }
@ -59,15 +61,13 @@ impl UserData for VM {
}); });
methods.add_method_mut("get_next_bus", |lua, this, name: String| { methods.add_method_mut("get_next_bus", |lua, this, name: String| {
format!( let id = this
"{}.{}", .bus_ids
name.clone(), .entry(name.clone())
this.bus_ids .and_modify(|x| *x += 1)
.entry(name) .or_insert(0);
.and_modify(|x| *x += 1)
.or_insert(0) format!("{}.{}", name, id).to_lua(lua)
)
.to_lua(lua)
}); });
methods.add_method_mut("get_counter", |lua, this, args: (String, usize)| { methods.add_method_mut("get_counter", |lua, this, args: (String, usize)| {
@ -107,7 +107,7 @@ impl UserData for VoreLuaWeakStorage {
let strong = weak let strong = weak
.0 .0
.upgrade() .upgrade()
.ok_or(LuaError::custom("vore storage has expired"))?; .ok_or_else(|| LuaError::custom("vore storage has expired"))?;
let mut this = strong let mut this = strong
.try_lock() .try_lock()
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?; .map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
@ -126,7 +126,7 @@ impl UserData for VoreLuaWeakStorage {
let strong = weak let strong = weak
.0 .0
.upgrade() .upgrade()
.ok_or(LuaError::custom("vore storage has expired"))?; .ok_or_else(|| LuaError::custom("vore storage has expired"))?;
let mut this = strong let mut this = strong
.try_lock() .try_lock()
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?; .map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
@ -145,7 +145,7 @@ impl UserData for VoreLuaWeakStorage {
let strong = weak let strong = weak
.0 .0
.upgrade() .upgrade()
.ok_or(LuaError::custom("vore storage has expired"))?; .ok_or_else(|| LuaError::custom("vore storage has expired"))?;
let this = strong let this = strong
.try_lock() .try_lock()
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?; .map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
@ -168,13 +168,16 @@ impl UserData for VoreLuaWeakStorage {
methods.add_method( methods.add_method(
"add_disk", "add_disk",
|lua, weak, args: (VM, mlua::Table, u64, mlua::Table)| -> Result<Value, mlua::Error> { |lua,
let (vm, instance, index, disk): (VM, mlua::Table, u64, Table) = args; weak,
args: (VirtualMachine, mlua::Table, u64, mlua::Table)|
-> Result<Value, mlua::Error> {
let (vm, instance, index, disk): (VirtualMachine, mlua::Table, u64, Table) = args;
let function = { let function = {
let strong = weak let strong = weak
.0 .0
.upgrade() .upgrade()
.ok_or(LuaError::custom("vore storage has expired"))?; .ok_or_else(|| LuaError::custom("vore storage has expired"))?;
let this = strong let this = strong
.try_lock() .try_lock()
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?; .map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
@ -223,11 +226,16 @@ impl QemuCommandBuilder {
global: &GlobalConfig, global: &GlobalConfig,
working_dir: PathBuf, working_dir: PathBuf,
) -> Result<QemuCommandBuilder, anyhow::Error> { ) -> Result<QemuCommandBuilder, anyhow::Error> {
let lua = Path::new(GLOBAL_CONFIG_LOCATION).parent().unwrap().join(&global.qemu.script); let lua = Path::new(VORE_CONFIG)
.parent()
.unwrap()
.join(&global.qemu.script);
let builder = QemuCommandBuilder { let builder = QemuCommandBuilder {
lua: Lua::new(), lua: Lua::new(),
script: fs::read_to_string(&lua).with_context(|| format!("Failed to load lua qemu command build script ({:?})", lua))?, script: fs::read_to_string(&lua).with_context(|| {
format!("Failed to load lua qemu command build script ({:?})", lua)
})?,
storage: VoreLuaStorage::new(working_dir), storage: VoreLuaStorage::new(working_dir),
}; };
@ -260,7 +268,7 @@ impl QemuCommandBuilder {
.eval::<()>() .eval::<()>()
.context("Failed to run the configured qemu lua script")?; .context("Failed to run the configured qemu lua script")?;
let item = VM::default(); let item = VirtualMachine::default();
let multi = MultiValue::from_vec(vec![self.lua.to_value(config)?, item.to_lua(&self.lua)?]); let multi = MultiValue::from_vec(vec![self.lua.to_value(config)?, item.to_lua(&self.lua)?]);
let working_dir = { self.storage.0.lock().unwrap().working_dir.clone() }; let working_dir = { self.storage.0.lock().unwrap().working_dir.clone() };
@ -277,22 +285,21 @@ impl QemuCommandBuilder {
anyhow::bail!("No qemu build command registered in lua script"); anyhow::bail!("No qemu build command registered in lua script");
}; };
let mut vm_instance = build_command.call::<MultiValue, VM>(multi)?; let mut vm_instance = build_command.call::<MultiValue, VirtualMachine>(multi)?;
let mut cmd: Vec<String> = vec![]; // Weird building way is for clarity sake
cmd.push("-name".to_string()); let mut cmd: Vec<String> = vec![
cmd.push(format!("guest={},debug-threads=on", config.name)); "-name".into(),
format!("guest={},debug-threads=on", config.name),
// Don't start the machine // Don't start the machine
cmd.push("-S".to_string()); "-S".into(),
// Set timestamps on log
// Set timestamps on log "-msg".into(),
cmd.push("-msg".to_string()); "timestamp=on".into(),
cmd.push("timestamp=on".to_string()); // Drop privileges as soon as possible
"-runas".into(),
// Drop privileges as soon as possible "nobody".into(),
cmd.push("-runas".to_string()); ];
cmd.push("nobody".to_string());
let working_dir = working_dir let working_dir = working_dir
.to_str() .to_str()

@ -9,13 +9,13 @@ macro_rules! define_requests {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "query", rename_all = "snake_case")] #[serde(tag = "query", rename_all = "snake_case")]
pub enum AllRequests { pub enum AllRequests {
$($name(paste! { [<$name Request >] })),+ $($name(Box<paste! { [<$name Request >] }>)),+
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "answer", rename_all = "snake_case")] #[serde(tag = "answer", rename_all = "snake_case")]
pub enum AllResponses { pub enum AllResponses {
$($name(paste! { [<$name Response >] })),+ $($name(Box<paste! { [<$name Response >] }>)),+
} }
$( $(
@ -29,13 +29,13 @@ macro_rules! define_requests {
type Response = [<$name Response>]; type Response = [<$name Response>];
fn into_enum(self) -> AllRequests { fn into_enum(self) -> AllRequests {
AllRequests::$name(self) AllRequests::$name(Box::new(self))
} }
} }
impl Response for [<$name Response>] { impl Response for [<$name Response>] {
fn into_enum(self) -> AllResponses { fn into_enum(self) -> AllResponses {
AllResponses::$name(self) AllResponses::$name(Box::new(self))
} }
} }
} }

@ -35,7 +35,7 @@ impl CommandCenter {
let mut str = serde_json::to_string(&answer)?; let mut str = serde_json::to_string(&answer)?;
str.push('\n'); str.push('\n');
return Ok(str); Ok(str)
} }
pub fn read_command(request: &str) -> Result<Command, anyhow::Error> { pub fn read_command(request: &str) -> Result<Command, anyhow::Error> {

@ -1,56 +1,31 @@
use crate::{GlobalConfig, InstanceConfig, QemuCommandBuilder}; #![cfg(feature = "host")]
use crate::cpu_list::CpuList;
use crate::{
GlobalConfig, InstanceConfig, QemuCommandBuilder, VfioConfig, VirtualMachineInfo,
VirtualMachineState,
};
use anyhow::{Context, Error}; use anyhow::{Context, Error};
use beau_collector::BeauCollector; use beau_collector::BeauCollector;
use qapi::qmp::{QMP, Event}; use libc::{cpu_set_t, sched_setaffinity, CPU_SET};
use qapi::{Qmp}; use qapi::qmp::{Event, QMP};
use std::{fmt, mem}; use qapi::Qmp;
use std::fmt::{Debug, Formatter, Display}; use qapi_qmp::QmpCommand;
use std::fs::{read_link, OpenOptions, read_dir}; use std::fmt::{Debug, Formatter};
use std::fs::{read_dir, read_link, OpenOptions};
use std::io; use std::io;
use std::io::{BufReader, ErrorKind, Read, Write}; use std::io::{BufReader, ErrorKind, Read, Write};
use std::option::Option::Some; use std::option::Option::Some;
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::path::{PathBuf, Path}; use std::os::unix::prelude::AsRawFd;
use std::path::{Path, PathBuf};
use std::process::{Child, Command}; use std::process::{Child, Command};
use std::result::Result::Ok; use std::result::Result::Ok;
use std::slice::Iter;
use std::str::FromStr;
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use qapi_qmp::QmpCommand; use std::{fmt, mem};
use std::str::FromStr;
use libc::{cpu_set_t, CPU_SET, sched_setaffinity};
use crate::cpu_list::CpuList;
use std::os::unix::prelude::AsRawFd;
use serde::{Deserialize, Serialize};
#[derive(Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum VirtualMachineState {
Loaded,
Prepared,
Stopped,
Paused,
Running,
}
impl Display for VirtualMachineState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
VirtualMachineState::Loaded => write!(f, "loaded"),
VirtualMachineState::Prepared => write!(f, "prepared"),
VirtualMachineState::Stopped => write!(f, "stopped"),
VirtualMachineState::Paused => write!(f, "paused"),
VirtualMachineState::Running => write!(f, "running")
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VirtualMachineInfo {
pub name: String,
pub working_dir: PathBuf,
pub config: InstanceConfig,
pub state: VirtualMachineState,
}
#[derive(Debug)] #[derive(Debug)]
pub struct VirtualMachine { pub struct VirtualMachine {
@ -60,6 +35,7 @@ pub struct VirtualMachine {
global_config: GlobalConfig, global_config: GlobalConfig,
process: Option<Child>, process: Option<Child>,
control_socket: Option<ControlSocket>, control_socket: Option<ControlSocket>,
quit_after_shutdown: bool,
} }
struct ControlSocket { struct ControlSocket {
@ -91,9 +67,14 @@ impl VirtualMachine {
global_config: global_config.clone(), global_config: global_config.clone(),
process: None, process: None,
control_socket: None, control_socket: None,
quit_after_shutdown: true,
} }
} }
pub fn vfio_devices(&self) -> Iter<'_, VfioConfig> {
self.config.vfio.iter()
}
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.config.name &self.config.name
} }
@ -104,6 +85,7 @@ impl VirtualMachine {
working_dir: self.working_dir.clone(), working_dir: self.working_dir.clone(),
config: self.config.clone(), config: self.config.clone(),
state: self.state, state: self.state,
quit_after_shutdown: self.quit_after_shutdown,
} }
} }
@ -128,7 +110,8 @@ impl VirtualMachine {
let mut shm = vec![]; let mut shm = vec![];
if self.config.looking_glass.enabled { if self.config.looking_glass.enabled {
if self.config.looking_glass.mem_path.is_empty() { if self.config.looking_glass.mem_path.is_empty() {
self.config.looking_glass.mem_path = format!("/dev/shm/vore/{}/looking-glass", self.config.name); self.config.looking_glass.mem_path =
format!("/dev/shm/vore/{}/looking-glass", self.config.name);
} }
shm.push(&self.config.looking_glass.mem_path); shm.push(&self.config.looking_glass.mem_path);
@ -142,12 +125,15 @@ impl VirtualMachine {
shm.push(&self.config.scream.mem_path); shm.push(&self.config.scream.mem_path);
} }
shm shm.into_iter()
.into_iter()
.map(|x| Path::new(x)) .map(|x| Path::new(x))
.filter_map(|x| x.parent()) .filter_map(|x| x.parent())
.filter(|x| !x.is_dir()) .filter(|x| !x.is_dir())
.map(|x| std::fs::create_dir_all(&x).with_context(|| format!("Failed creating directories for shared memory ({:?})", x))) .map(|x| {
std::fs::create_dir_all(&x).with_context(|| {
format!("Failed creating directories for shared memory ({:?})", x)
})
})
.collect() .collect()
} }
@ -155,7 +141,12 @@ impl VirtualMachine {
let mut sockets = vec![]; let mut sockets = vec![];
if self.config.spice.enabled { if self.config.spice.enabled {
if self.config.spice.socket_path.is_empty() { if self.config.spice.socket_path.is_empty() {
self.config.spice.socket_path = self.working_dir.join("spice.sock").to_str().unwrap().to_string(); self.config.spice.socket_path = self
.working_dir
.join("spice.sock")
.to_str()
.unwrap()
.to_string();
} }
sockets.push(&self.config.spice.socket_path); sockets.push(&self.config.spice.socket_path);
@ -166,7 +157,11 @@ impl VirtualMachine {
.map(|x| Path::new(x)) .map(|x| Path::new(x))
.filter_map(|x| x.parent()) .filter_map(|x| x.parent())
.filter(|x| !x.is_dir()) .filter(|x| !x.is_dir())
.map(|x| std::fs::create_dir_all(&x).with_context(|| format!("Failed creating directories for shared memory ({:?})", x))) .map(|x| {
std::fs::create_dir_all(&x).with_context(|| {
format!("Failed creating directories for shared memory ({:?})", x)
})
})
.collect() .collect()
} }
@ -214,84 +209,97 @@ impl VirtualMachine {
Ok(_) => {} Ok(_) => {}
} }
self.config.vfio.iter().map(|vfio| { self.config
let pci_driver_path = format!("/sys/bus/pci/devices/{:#}/driver", vfio.address); .vfio
.iter()
let driver = match read_link(&pci_driver_path) { .map(|vfio| VirtualMachine::prepare_vfio_device(execute_fixes, force, vfio))
Ok(driver_link) => { .collect::<Vec<_>>()
let driver_path = driver_link.to_str().ok_or_else(|| { }
anyhow::anyhow!(
"Path to device driver for PCI device at {} is not valid utf-8", pub fn should_auto_start(&self) -> bool {
vfio.address self.config.auto_start
) }
})?;
let driver = driver_path.split("/").last().ok_or_else(|| { pub fn prepare_vfio_device(
anyhow::anyhow!( execute_fixes: bool,
force: bool,
vfio: &VfioConfig,
) -> Result<(), Error> {
let pci_driver_path = format!("/sys/bus/pci/devices/{:#}/driver", vfio.address);
let driver = match read_link(&pci_driver_path) {
Ok(driver_link) => {
let driver_path = driver_link.to_str().ok_or_else(|| {
anyhow::anyhow!(
"Path to device driver for PCI device at {} is not valid utf-8",
vfio.address
)
})?;
let driver = driver_path.split('/').last().ok_or_else(|| {
anyhow::anyhow!(
"Path to device driver for PCI device at {} doesn't have a path to a driver", "Path to device driver for PCI device at {} doesn't have a path to a driver",
vfio.address vfio.address
) )
})?; })?;
driver.to_string() driver.to_string()
} }
Err(err) if err.kind() == ErrorKind::NotFound => "".to_string(), Err(err) if err.kind() == ErrorKind::NotFound => "".to_string(),
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
let is_blacklisted = AUTO_UNBIND_BLACKLIST.contains(&driver.as_str()) && !force; let is_blacklisted = AUTO_UNBIND_BLACKLIST.contains(&driver.as_str()) && !force;
if driver != "vfio-pci" && (!execute_fixes || is_blacklisted) { if driver != "vfio-pci" && (!execute_fixes || is_blacklisted) {
if !driver.is_empty() && is_blacklisted { if !driver.is_empty() && is_blacklisted {
anyhow::bail!("PCI device {} it's current driver is {}, but to be used with VFIO needs to be set to vfio-pci, this driver ({1}) has been blacklisted from automatic rebinding because it can't be cleanly unbound, please make sure this device is unbound before running vore", vfio.address, driver) anyhow::bail!("PCI device {} it's current driver is {}, but to be used with VFIO needs to be set to vfio-pci, this driver ({1}) has been blacklisted from automatic rebinding because it can't be cleanly unbound, please make sure this device is unbound before running vore", vfio.address, driver)
} else if !driver.is_empty() { } else if !driver.is_empty() {
anyhow::bail!("PCI device {} it's current driver is {}, but to be used with VFIO needs to be set to vfio-pci", vfio.address, driver) anyhow::bail!("PCI device {} it's current driver is {}, but to be used with VFIO needs to be set to vfio-pci", vfio.address, driver)
} else { } else {
anyhow::bail!("PCI device at {} currently has no driver, but to be used with VFIO needs to be set to vfio-pci", vfio.address) anyhow::bail!("PCI device at {} currently has no driver, but to be used with VFIO needs to be set to vfio-pci", vfio.address)
}
} }
}
if driver != "vfio-pci" && execute_fixes && !is_blacklisted { if driver != "vfio-pci" && execute_fixes && !is_blacklisted {
let address = format!("{:#}\n", vfio.address).into_bytes(); let address = format!("{:#}\n", vfio.address).into_bytes();
if !driver.is_empty() { if !driver.is_empty() {
// Unbind the PCI device from the current driver // Unbind the PCI device from the current driver
let mut unbind = std::fs::OpenOptions::new().append(true).open(format!( let mut unbind = std::fs::OpenOptions::new().append(true).open(format!(
"/sys/bus/pci/devices/{:#}/driver/unbind", "/sys/bus/pci/devices/{:#}/driver/unbind",
vfio.address vfio.address
))?; ))?;
unbind.write_all(&address)?; unbind.write_all(&address)?;
} }
{ {
// Set a driver override // Set a driver override
let mut driver_override = OpenOptions::new().append(true).open(format!( let mut driver_override = OpenOptions::new().append(true).open(format!(
"/sys/bus/pci/devices/{:#}/driver_override", "/sys/bus/pci/devices/{:#}/driver_override",
vfio.address vfio.address
))?; ))?;
driver_override.write_all(b"vfio-pci\n")?; driver_override.write_all(b"vfio-pci\n")?;
} }
{ {
// Probe the PCI device so the driver override is picked up // Probe the PCI device so the driver override is picked up
let mut probe = OpenOptions::new() let mut probe = OpenOptions::new()
.append(true) .append(true)
.open("/sys/bus/pci/drivers_probe")?; .open("/sys/bus/pci/drivers_probe")?;
probe.write_all(&address)?; probe.write_all(&address)?;
} }
let new_link = read_link(&pci_driver_path)?; let new_link = read_link(&pci_driver_path)?;
if !new_link.ends_with("vfio-pci") { if !new_link.ends_with("vfio-pci") {
anyhow::bail!("Tried to bind {} to vfio-pci but failed to do so (see /sys/bus/pci/devices/{:#} for more info)", vfio.address, vfio.address) anyhow::bail!("Tried to bind {} to vfio-pci but failed to do so (see /sys/bus/pci/devices/{:#} for more info)", vfio.address, vfio.address)
}
} }
}
Ok(()) Ok(())
})
.collect::<Vec<_>>()
} }
pub fn get_cmd_line(&self) -> Result<Vec<String>, anyhow::Error> { pub fn get_cmd_line(&self) -> Result<Vec<String>, anyhow::Error> {
@ -321,7 +329,11 @@ impl VirtualMachine {
continue; continue;
} }
let res = entry.file_name().to_str().ok_or_else(|| anyhow::anyhow!("")).and_then(|x| usize::from_str(x).map_err(From::from)); let res = entry
.file_name()
.to_str()
.ok_or_else(|| anyhow::anyhow!(""))
.and_then(|x| usize::from_str(x).map_err(From::from));
if res.is_err() { if res.is_err() {
continue; continue;
} }
@ -330,7 +342,11 @@ impl VirtualMachine {
let name = entry.path().join("comm"); let name = entry.path().join("comm");
let comm = std::fs::read_to_string(name)?; let comm = std::fs::read_to_string(name)?;
if comm.starts_with("CPU ") { if comm.starts_with("CPU ") {
let nr = comm.chars().skip(4).take_while(|x| x.is_ascii_digit()).collect::<String>(); let nr = comm
.chars()
.skip(4)
.take_while(|x| x.is_ascii_digit())
.collect::<String>();
let cpu_id = usize::from_str(&nr).unwrap(); let cpu_id = usize::from_str(&nr).unwrap();
kvm_threads.push((tid, cpu_id)); kvm_threads.push((tid, cpu_id));
} }
@ -358,10 +374,12 @@ impl VirtualMachine {
qmp.qmp.nop()?; qmp.qmp.nop()?;
} }
self.process_qmp_events() self.process_qmp_events()?;
Ok(())
} }
fn process_qmp_events(&mut self) -> Result<(), anyhow::Error> { fn process_qmp_events(&mut self) -> anyhow::Result<()> {
let events = if let Some(qmp) = self.control_socket.as_mut() { let events = if let Some(qmp) = self.control_socket.as_mut() {
// While we could iter, we keep hold of the mutable reference, so it's easier to just collect the events // While we could iter, we keep hold of the mutable reference, so it's easier to just collect the events
qmp.qmp.events().collect::<Vec<_>>() qmp.qmp.events().collect::<Vec<_>>()
@ -370,11 +388,11 @@ impl VirtualMachine {
}; };
for event in events { for event in events {
println!("Event: {:?}", event); log::info!("vm {} got event: {:?}", self.name(), event);
match event { match event {
Event::STOP { .. } => { Event::STOP { .. } => {
if self.state != VirtualMachineState::Stopped { if self.state == VirtualMachineState::Running {
self.state = VirtualMachineState::Paused; self.state = VirtualMachineState::Paused;
} }
} }
@ -383,6 +401,10 @@ impl VirtualMachine {
} }
Event::SHUTDOWN { .. } => { Event::SHUTDOWN { .. } => {
self.state = VirtualMachineState::Stopped; self.state = VirtualMachineState::Stopped;
if self.quit_after_shutdown {
self.quit()?;
}
} }
_ => {} _ => {}
@ -414,7 +436,10 @@ impl VirtualMachine {
} }
pub fn stop(&mut self) -> Result<(), anyhow::Error> { pub fn stop(&mut self) -> Result<(), anyhow::Error> {
if self.process.is_none() || self.control_socket.is_none() || self.state == VirtualMachineState::Stopped { if self.process.is_none()
|| self.control_socket.is_none()
|| self.state == VirtualMachineState::Stopped
{
return Ok(()); return Ok(());
} }
@ -433,17 +458,32 @@ impl VirtualMachine {
} }
match self.send_qmp_command(&qapi_qmp::quit {}) { match self.send_qmp_command(&qapi_qmp::quit {}) {
Err(err) if err.downcast_ref::<io::Error>().map_or(false, |x| x.kind() == io::ErrorKind::UnexpectedEof) => {} Err(err)
err => { err?; } if err.downcast_ref::<io::Error>().map_or(false, |x| {
x.kind() == io::ErrorKind::UnexpectedEof
|| x.kind() == io::ErrorKind::ConnectionReset
}) => {}
err => {
err?;
}
} }
self.control_socket = None;
self.state = VirtualMachineState::Prepared;
Ok(()) Ok(())
} }
fn wait(&mut self, duration: Option<Duration>, target_state: VirtualMachineState) -> Result<bool, anyhow::Error> { fn wait(
&mut self,
duration: Option<Duration>,
target_state: VirtualMachineState,
) -> Result<bool, anyhow::Error> {
let start = Instant::now(); let start = Instant::now();
while duration.map_or(true, |dur| (Instant::now() - start) < dur) { while duration.map_or(true, |dur| (Instant::now() - start) < dur) {
let has_socket = self.control_socket.as_mut() let has_socket = self
.control_socket
.as_mut()
.map(|x| x.qmp.nop()) .map(|x| x.qmp.nop())
.transpose()? .transpose()?
.is_some(); .is_some();
@ -480,7 +520,10 @@ impl VirtualMachine {
} }
let mut command = Command::new("qemu-system-x86_64"); let mut command = Command::new("qemu-system-x86_64");
command.args(self.get_cmd_line().context("Failed to generate qemu command line")?); command.args(
self.get_cmd_line()
.context("Failed to generate qemu command line")?,
);
self.process = Some(command.spawn()?); self.process = Some(command.spawn()?);
let mut res = || { let mut res = || {
@ -499,7 +542,7 @@ impl VirtualMachine {
unix_stream = UnixStream::connect(&qemu_control_socket); unix_stream = UnixStream::connect(&qemu_control_socket);
if let Some(proc) = self.process.as_mut() { if let Some(proc) = self.process.as_mut() {
if let Some(_) = proc.try_wait()? { if proc.try_wait()?.is_some() {
anyhow::bail!("QEMU quit early") anyhow::bail!("QEMU quit early")
} }
} }
@ -520,6 +563,18 @@ impl VirtualMachine {
self.pin_qemu_threads()?; self.pin_qemu_threads()?;
if self.config.looking_glass.enabled {
self.global_config
.vore
.chown(&self.config.looking_glass.mem_path)?;
}
if self.config.spice.enabled {
self.global_config
.vore
.chown(&self.config.spice.socket_path)?;
}
control_socket control_socket
.qmp .qmp
.execute(&qapi_qmp::cont {}) .execute(&qapi_qmp::cont {})
@ -588,4 +643,4 @@ impl Write for CloneableUnixStream {
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
self.lock()?.flush() self.lock()?.flush()
} }
} }

@ -0,0 +1,36 @@
use std::fmt::{Display, Formatter};
use std::fmt;
use crate::InstanceConfig;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum VirtualMachineState {
Loaded,
Prepared,
Stopped,
Paused,
Running,
}
impl Display for VirtualMachineState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
VirtualMachineState::Loaded => write!(f, "loaded"),
VirtualMachineState::Prepared => write!(f, "prepared"),
VirtualMachineState::Stopped => write!(f, "stopped"),
VirtualMachineState::Paused => write!(f, "paused"),
VirtualMachineState::Running => write!(f, "running")
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VirtualMachineInfo {
pub name: String,
pub working_dir: PathBuf,
pub config: InstanceConfig,
pub state: VirtualMachineState,
pub quit_after_shutdown: bool,
}

@ -8,7 +8,6 @@ args:
help: "Connect to the specified socket" help: "Connect to the specified socket"
required: false required: false
takes_value: true takes_value: true
default_value: "/run/vore.sock"
long: conn long: conn
short: c short: c
@ -102,6 +101,7 @@ subcommands:
visible_alias: lg visible_alias: lg
args: args:
- vm-name: - vm-name:
long: vm
help: "VM to start looking glass instance for, if not given the ONLY running instance will be used" help: "VM to start looking glass instance for, if not given the ONLY running instance will be used"
required: false required: false
takes_value: true takes_value: true

@ -1,13 +1,14 @@
mod client; mod client;
use vore_core::{init_logging, VirtualMachineInfo};
use crate::client::Client; use crate::client::Client;
use clap::{App, ArgMatches};
use std::{fs, mem};
use anyhow::Context; use anyhow::Context;
use clap::{App, ArgMatches};
use std::option::Option::Some; use std::option::Option::Some;
use std::process::Command;
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
use std::process::Command;
use std::{fs, mem};
use vore_core::consts::VORE_SOCKET;
use vore_core::{init_logging, VirtualMachineInfo};
fn main() { fn main() {
init_logging(); init_logging();
@ -21,11 +22,9 @@ fn main_res() -> anyhow::Result<()> {
let yaml = clap::load_yaml!("../clap.yml"); let yaml = clap::load_yaml!("../clap.yml");
let app: App = App::from(yaml); let app: App = App::from(yaml);
let matches = app.get_matches(); let matches = app.get_matches();
let client = Client::connect(matches.value_of("vored-socket").unwrap())?; let client = Client::connect(matches.value_of("vored-socket").unwrap_or(VORE_SOCKET))?;
let mut vore = VoreApp { let mut vore = VoreApp { client };
client
};
match matches.subcommand() { match matches.subcommand() {
("load", Some(args)) => { ("load", Some(args)) => {
@ -52,17 +51,15 @@ fn main_res() -> anyhow::Result<()> {
vore.looking_glass(args)?; vore.looking_glass(args)?;
} }
("daemon", Some(args)) => { ("daemon", Some(args)) => match args.subcommand() {
match args.subcommand() { ("version", _) => {
("version", _) => { vore.daemon_version()?;
vore.daemon_version()?; }
}
(s, _) => { (s, _) => {
log::error!("Subcommand daemon.{} not implemented", s); log::error!("Subcommand daemon.{} not implemented", s);
}
} }
} },
(s, _) => { (s, _) => {
log::error!("Subcommand {} not implemented", s); log::error!("Subcommand {} not implemented", s);
@ -72,20 +69,22 @@ fn main_res() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
struct LoadVMOptions { struct LoadVirtualMachineOptions {
config: String, config: String,
cd_roms: Vec<String>, cd_roms: Vec<String>,
save: bool, save: bool,
} }
fn get_load_vm_options(args: &ArgMatches) -> anyhow::Result<LoadVMOptions> { fn get_load_vm_options(args: &ArgMatches) -> anyhow::Result<LoadVirtualMachineOptions> {
let vm_config_path = args.value_of("vm-config").unwrap(); let vm_config_path = args.value_of("vm-config").unwrap();
let config = fs::read_to_string(vm_config_path) let config = fs::read_to_string(vm_config_path)
.with_context(|| format!("Failed to read vm config at {}", vm_config_path))?; .with_context(|| format!("Failed to read vm config at {}", vm_config_path))?;
Ok(LoadVMOptions { Ok(LoadVirtualMachineOptions {
config, config,
cd_roms: args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()), cd_roms: args
.values_of("cdrom")
.map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()),
save: args.is_present("save"), save: args.is_present("save"),
}) })
} }
@ -102,11 +101,13 @@ impl VoreApp {
pub fn get_vm(&mut self, args: &ArgMatches) -> anyhow::Result<VirtualMachineInfo> { pub fn get_vm(&mut self, args: &ArgMatches) -> anyhow::Result<VirtualMachineInfo> {
let mut items = self.client.list_vms()?; let mut items = self.client.list_vms()?;
if let Some(vm_name) = args.value_of("vm-name") { if let Some(vm_name) = args.value_of("vm-name") {
items.into_iter().find(|x| x.name == vm_name) items
.into_iter()
.find(|x| x.name == vm_name)
.with_context(|| format!("Couldn't find VM with the name '{}'", vm_name)) .with_context(|| format!("Couldn't find VM with the name '{}'", vm_name))
} else { } else {
match (items.len(), items.pop()) { match (items.len(), items.pop()) {
(amount, Some(x)) if amount == 1 => return Ok(x), (amount, Some(x)) if amount == 1 => Ok(x),
(0, None) => anyhow::bail!("There are no VM's loaded"), (0, None) => anyhow::bail!("There are no VM's loaded"),
_ => anyhow::bail!("Multiple VM's are loaded, please specify one"), _ => anyhow::bail!("Multiple VM's are loaded, please specify one"),
} }
@ -122,7 +123,9 @@ impl VoreApp {
fn load(&mut self, args: &ArgMatches) -> anyhow::Result<()> { fn load(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
let vm_options = get_load_vm_options(args)?; let vm_options = get_load_vm_options(args)?;
let vm_info = self.client.load_vm(&vm_options.config, vm_options.save, vm_options.cd_roms)?; let vm_info =
self.client
.load_vm(&vm_options.config, vm_options.save, vm_options.cd_roms)?;
log::info!("Loaded VM {}", vm_info.name); log::info!("Loaded VM {}", vm_info.name);
Ok(()) Ok(())
} }
@ -139,13 +142,21 @@ impl VoreApp {
fn prepare(&mut self, args: &ArgMatches) -> anyhow::Result<()> { fn prepare(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
let name = self.get_vm_name(args)?; let name = self.get_vm_name(args)?;
self.client.prepare(name, args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()))?; self.client.prepare(
name,
args.values_of("cdrom")
.map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()),
)?;
Ok(()) Ok(())
} }
fn start(&mut self, args: &ArgMatches) -> anyhow::Result<()> { fn start(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
let name = self.get_vm_name(args)?; let name = self.get_vm_name(args)?;
self.client.start(name, args.values_of("cdrom").map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()))?; self.client.start(
name,
args.values_of("cdrom")
.map_or(vec![], |x| x.map(|x| x.to_string()).collect::<Vec<_>>()),
)?;
Ok(()) Ok(())
} }
@ -155,7 +166,9 @@ impl VoreApp {
anyhow::bail!("VM '{}' has no looking glass", vm.name); anyhow::bail!("VM '{}' has no looking glass", vm.name);
} }
let mut command = Command::new(std::env::var("LOOKING_GLASS").unwrap_or("looking-glass-client".to_string())); let mut command = Command::new(
std::env::var("LOOKING_GLASS").unwrap_or_else(|_| "looking-glass-client".to_string()),
);
if vm.config.spice.enabled { if vm.config.spice.enabled {
command.args(&["-c", &vm.config.spice.socket_path, "-p", "0"]); command.args(&["-c", &vm.config.spice.socket_path, "-p", "0"]);
} else { } else {
@ -163,7 +176,10 @@ impl VoreApp {
} }
command.args(&["-f", &vm.config.looking_glass.mem_path]); command.args(&["-f", &vm.config.looking_glass.mem_path]);
command.args(args.values_of("looking-glass-args").map_or(vec![], |x| x.into_iter().collect::<Vec<_>>())); command.args(
args.values_of("looking-glass-args")
.map_or(vec![], |x| x.into_iter().collect::<Vec<_>>()),
);
mem::drop(self); mem::drop(self);
command.exec(); command.exec();

@ -8,7 +8,7 @@ edition = "2018"
[dependencies] [dependencies]
anyhow = "1.0.40" anyhow = "1.0.40"
vore-core = { path = "../vore-core" } vore-core = { path = "../vore-core", features = ["host"] }
polling = "2.0.3" polling = "2.0.3"
log = "0.4.14" log = "0.4.14"
pretty_env_logger = "0.3" pretty_env_logger = "0.3"

@ -1,24 +1,27 @@
use std::collections::{HashMap};
use vore_core::{VirtualMachine, InstanceConfig, GlobalConfig, GLOBAL_CONFIG_LOCATION};
use std::os::unix::net::{UnixListener, SocketAddr, UnixStream};
use polling::{Poller, Event};
use std::time::Duration;
use anyhow::Context; use anyhow::Context;
use std::{mem, io}; use polling::{Event, Poller};
use std::io::{Read, Write}; use signal_hook::consts::{SIGHUP, SIGINT, SIGTERM};
use vore_core::rpc::{CommandCenter, Response, Command, AllRequests, AllResponses}; use signal_hook::iterator::{Handle, Signals, SignalsInfo};
use vore_core::rpc; use signal_hook::low_level::signal_name;
use signal_hook::low_level::{signal_name}; use std::collections::HashMap;
use signal_hook::consts::{SIGINT, SIGTERM, SIGHUP};
use std::path::PathBuf;
use std::str::FromStr;
use signal_hook::iterator::{SignalsInfo, Signals, Handle};
use std::os::unix::io::AsRawFd;
use std::ffi::CStr; use std::ffi::CStr;
use std::fs;
use std::fs::{read_dir, read_to_string, DirEntry};
use std::io::{Read, Write};
use std::mem::size_of; use std::mem::size_of;
use std::os::unix::io::AsRawFd;
use std::os::unix::net::{SocketAddr, UnixListener, UnixStream};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use std::{io, mem};
use vore_core::consts::{VORE_CONFIG, VORE_DIRECTORY, VORE_SOCKET};
use vore_core::rpc::{AllRequests, AllResponses, Command, CommandCenter, Response};
use vore_core::{rpc, VirtualMachineInfo};
use vore_core::{GlobalConfig, InstanceConfig, VirtualMachine};
#[derive(Debug)] #[derive(Debug)]
struct RPCConnection { struct RpcConnection {
stream: UnixStream, stream: UnixStream,
address: SocketAddr, address: SocketAddr,
buffer: Vec<u8>, buffer: Vec<u8>,
@ -27,7 +30,7 @@ struct RPCConnection {
pid: i32, pid: i32,
} }
impl Write for RPCConnection { impl Write for RpcConnection {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stream.write(buf) self.stream.write(buf)
} }
@ -37,16 +40,20 @@ impl Write for RPCConnection {
} }
} }
impl Read for RPCConnection { impl Read for RpcConnection {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream.read(buf) self.stream.read(buf)
} }
} }
#[allow(clippy::char_lit_as_u8)]
const NEWLINE: u8 = '\n' as u8; const NEWLINE: u8 = '\n' as u8;
impl RPCConnection { impl RpcConnection {
pub fn handle_input(&mut self, own_id: usize) -> Result<(bool, Vec<(usize, Command)>), anyhow::Error> { pub fn handle_input(
&mut self,
own_id: usize,
) -> Result<(bool, Vec<(usize, Command)>), anyhow::Error> {
let mut still_open = true; let mut still_open = true;
loop { loop {
let mut buffer = vec![0u8; 4096]; let mut buffer = vec![0u8; 4096];
@ -57,13 +64,21 @@ impl RPCConnection {
} }
Ok(amount) => self.buffer.extend_from_slice(&buffer[..amount]), Ok(amount) => self.buffer.extend_from_slice(&buffer[..amount]),
Err(err) if err.kind() == io::ErrorKind::WouldBlock => break, Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
Err(err) => return Err(err.into()) Err(err) => return Err(err.into()),
}; };
} }
let mut buffer = mem::take(&mut self.buffer); let mut buffer = mem::take(&mut self.buffer);
if still_open { if still_open {
self.buffer = buffer.split_off(buffer.iter().enumerate().rev().find(|(_, x)| **x == NEWLINE).map(|(idx, _)| idx + 1).unwrap_or(buffer.len())); self.buffer = buffer.split_off(
buffer
.iter()
.enumerate()
.rev()
.find(|(_, x)| **x == NEWLINE)
.map(|(idx, _)| idx + 1)
.unwrap_or(buffer.len()),
);
} }
let mut commands = vec![]; let mut commands = vec![];
@ -73,7 +88,6 @@ impl RPCConnection {
continue; continue;
} }
let lossy = String::from_utf8_lossy(part); let lossy = String::from_utf8_lossy(part);
match CommandCenter::read_command(&lossy) { match CommandCenter::read_command(&lossy) {
@ -94,9 +108,9 @@ impl RPCConnection {
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
enum EventTarget { enum EventTarget {
RPCListener, RpcListener,
Machine(String), Machine(String),
RPCConnection(usize), RpcConnection(usize),
None, None,
} }
@ -105,7 +119,7 @@ pub struct Daemon {
event_key_storage: Vec<EventTarget>, event_key_storage: Vec<EventTarget>,
global_config: GlobalConfig, global_config: GlobalConfig,
machines: HashMap<String, VirtualMachine>, machines: HashMap<String, VirtualMachine>,
connections: Vec<Option<RPCConnection>>, connections: Vec<Option<RpcConnection>>,
rpc_listener: UnixListener, rpc_listener: UnixListener,
socket_path: PathBuf, socket_path: PathBuf,
poller: Poller, poller: Poller,
@ -117,18 +131,22 @@ pub struct Daemon {
impl Daemon { impl Daemon {
pub fn new() -> Result<Daemon, anyhow::Error> { pub fn new() -> Result<Daemon, anyhow::Error> {
log::debug!("Loading global config ({})", GLOBAL_CONFIG_LOCATION); log::debug!("Loading global config ({})", VORE_CONFIG);
let toml = std::fs::read_to_string(GLOBAL_CONFIG_LOCATION)?; let toml = std::fs::read_to_string(VORE_CONFIG)?;
let global_config = GlobalConfig::load(&toml)?; let mut global_config = GlobalConfig::load(&toml)?;
log::debug!("Creating vore daemon"); log::debug!("Creating vore daemon");
let signals = Signals::new(&[SIGINT, SIGHUP])?; let signals = Signals::new(&[SIGINT, SIGHUP])?;
let handle = signals.handle(); let handle = signals.handle();
log::debug!("Bound signal handlers"); log::debug!("Bound signal handlers");
let poller = Poller::new().context("Failed to make poller")?; let poller = Poller::new().context("Failed to make poller")?;
let socket_path = PathBuf::from_str("/run/vore.sock")?; let socket_path = PathBuf::from_str(VORE_SOCKET)?;
let rpc_listener = UnixListener::bind(&socket_path).context("Failed to bind vore socket")?; let rpc_listener =
UnixListener::bind(&socket_path).context("Failed to bind vore socket")?;
global_config.vore.chown(socket_path.to_str().unwrap())?;
rpc_listener.set_nonblocking(true)?; rpc_listener.set_nonblocking(true)?;
log::debug!("Bound to /run/vore.sock"); log::debug!("Bound to {}", VORE_SOCKET);
let mut daemon = Daemon { let mut daemon = Daemon {
event_key_storage: vec![], event_key_storage: vec![],
@ -149,23 +167,106 @@ impl Daemon {
} }
pub fn init(&mut self) -> Result<(), anyhow::Error> { pub fn init(&mut self) -> Result<(), anyhow::Error> {
let new_key = self.add_target(EventTarget::RPCListener); let new_key = self.add_target(EventTarget::RpcListener);
self.poller.add(&self.rpc_listener, Event::readable(new_key))?; self.poller
.add(&self.rpc_listener, Event::readable(new_key))?;
Ok(()) Ok(())
} }
pub fn load_definitions(&mut self) -> Result<(), anyhow::Error> {
let vm_dir = PathBuf::from(format!("{}/definitions", VORE_DIRECTORY));
if !vm_dir.is_dir() {
return Ok(());
}
let dir_iter =
read_dir(&vm_dir).with_context(|| format!("Failed to list {:?} for vm's", &vm_dir))?;
let mut process = |entry: Result<DirEntry, io::Error>| -> anyhow::Result<()> {
let entry = entry?;
let file_name = entry.path();
let path = file_name.to_str().context("Entry has invalid UTF-8 path")?;
if !path.ends_with(".toml") {
return Ok(());
}
let toml = read_to_string(path)
.with_context(|| format!("Failed to read VM definition {}", path))?;
self.load_virtual_machine(&toml, None, false)?;
Ok(())
};
for entry in dir_iter {
if let Err(err) = process(entry) {
log::error!("Failed parsing entry in {:?}: {:?}", vm_dir, err);
}
}
Ok(())
}
pub fn reserve_vfio_devices(&mut self) {
for machine in self.machines.values() {
for vfio_device in machine.vfio_devices() {
if !vfio_device.reserve {
continue;
}
if let Err(err) = VirtualMachine::prepare_vfio_device(true, true, &vfio_device) {
log::error!(
"Failed to reserve PCI device {} for {}: {:?}",
vfio_device.address,
machine.name(),
err
);
} else {
log::info!(
"Reserved PCI device {} for {}",
vfio_device.address,
machine.name()
);
}
}
}
}
pub fn auto_start_machines(&mut self) {
for machine in self.machines.values_mut() {
if !machine.should_auto_start() {
continue;
}
if let Err(err) = machine.start() {
log::error!("Failed to auto-start {}: {:?}", machine.name(), err);
} else {
log::info!("Autostarted {}", machine.name());
}
}
}
pub fn run(&mut self) -> Result<(), anyhow::Error> { pub fn run(&mut self) -> Result<(), anyhow::Error> {
self.load_definitions()?;
self.reserve_vfio_devices();
self.auto_start_machines();
loop { loop {
let res = self.wait().context("Got error while waiting for new notifications"); let res = self
.wait()
.context("Got error while waiting for new notifications");
match res { match res {
// Interrupted is uh "always" when we get a signal // Interrupted is uh "always" when we get a signal
Err(err) if err.downcast_ref::<io::Error>().map(|x| x.kind() == io::ErrorKind::Interrupted).unwrap_or(false) => { Err(err)
if err
.downcast_ref::<io::Error>()
.map(|x| x.kind() == io::ErrorKind::Interrupted)
.unwrap_or(false) =>
{
if !self.handle_exit_code()? { if !self.handle_exit_code()? {
break; break;
} }
} }
err => err? err => err?,
} }
if !self.handle_event_queue()? { if !self.handle_event_queue()? {
@ -196,37 +297,63 @@ impl Daemon {
Ok(()) Ok(())
} }
pub fn load_virtual_machine(
&mut self,
toml: &str,
working_directory: Option<String>,
save: bool,
) -> anyhow::Result<VirtualMachineInfo> {
let config = InstanceConfig::from_toml(&toml)?;
if save {
let save_file = format!("{}/definitions/{}.toml", VORE_DIRECTORY, config.name);
let file_path = Path::new(&save_file);
if let Some(parent_dir) = file_path.parent() {
if !parent_dir.is_dir() {
fs::create_dir_all(parent_dir)?;
}
}
fs::write(&save_file, toml).with_context(|| {
format!(
"Failed to save vm definition for {} to {}",
config.name, save_file
)
})?;
}
let working_dir = working_directory
.unwrap_or_else(|| format!("{}/instance/{}", VORE_DIRECTORY, config.name));
let vm = VirtualMachine::new(config, &self.global_config, working_dir);
let info = vm.info();
self.mount_machine(vm);
Ok(info)
}
pub fn handle_command(&mut self, command: &Command) -> Result<AllResponses, anyhow::Error> { pub fn handle_command(&mut self, command: &Command) -> Result<AllResponses, anyhow::Error> {
let resp = match &command.data { let resp = match &command.data {
AllRequests::Info(_) => { AllRequests::Info(_) => rpc::InfoResponse {
rpc::InfoResponse { name: "vore".to_string(),
name: "vore".to_string(), version: format!(
version: format!("{}.{}.{}{}", "{}.{}.{}{}",
env!("CARGO_PKG_VERSION_MAJOR"), env!("CARGO_PKG_VERSION_MAJOR"),
env!("CARGO_PKG_VERSION_MINOR"), env!("CARGO_PKG_VERSION_MINOR"),
env!("CARGO_PKG_VERSION_PATCH"), env!("CARGO_PKG_VERSION_PATCH"),
option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")), option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")
} ),
.into_enum()
} }
AllRequests::List(_) => { .into_enum(),
rpc::ListResponse { AllRequests::List(_) => rpc::ListResponse {
items: self.machines.values().map(|x| x.info()).collect() items: self.machines.values().map(|x| x.info()).collect(),
}
.into_enum()
} }
AllRequests::Load(val) => { .into_enum(),
let config = InstanceConfig::from_toml(&val.toml)?; AllRequests::Load(val) => rpc::LoadResponse {
let working_dir = val.working_directory.as_ref().cloned().unwrap_or_else(|| format!("/var/lib/vore/{}", config.name)); info: self.load_virtual_machine(
let vm = VirtualMachine::new(config, &self.global_config, working_dir); &val.toml,
let info = vm.info(); val.working_directory.as_ref().cloned(),
self.mount_machine(vm); val.save,
)?,
rpc::LoadResponse {
info,
}
.into_enum()
} }
.into_enum(),
AllRequests::Prepare(val) => { AllRequests::Prepare(val) => {
if let Some(machine) = self.machines.get_mut(&val.name) { if let Some(machine) = self.machines.get_mut(&val.name) {
machine.prepare(true, false)?; machine.prepare(true, false)?;
@ -280,7 +407,11 @@ impl Daemon {
pub fn handle_exit_code(&mut self) -> Result<bool, anyhow::Error> { pub fn handle_exit_code(&mut self) -> Result<bool, anyhow::Error> {
for signal in self.signals.pending() { for signal in self.signals.pending() {
log::info!("Received signal {} ({})", signal_name(signal).unwrap_or("<unknown>"), signal); log::info!(
"Received signal {} ({})",
signal_name(signal).unwrap_or("<unknown>"),
signal
);
match signal { match signal {
SIGINT | SIGTERM => return Ok(false), SIGINT | SIGTERM => return Ok(false),
_ => {} _ => {}
@ -297,8 +428,9 @@ impl Daemon {
log::debug!("Handling {:?} from target {:?}", event, item); log::debug!("Handling {:?} from target {:?}", event, item);
match item { match item {
EventTarget::RPCListener => { EventTarget::RpcListener => {
self.poller.modify(&self.rpc_listener, Event::readable(event.key))?; self.poller
.modify(&self.rpc_listener, Event::readable(event.key))?;
self.accept_rpc_connections()?; self.accept_rpc_connections()?;
} }
EventTarget::Machine(name) if self.machines.contains_key(&name) => { EventTarget::Machine(name) if self.machines.contains_key(&name) => {
@ -306,15 +438,27 @@ impl Daemon {
machine.boop()?; machine.boop()?;
} }
if let Some(control_socket) = self.machines.get(&name).and_then(|x| x.control_stream()) { if let Some(control_socket) =
self.poller.modify(control_socket, Event::readable(event.key))?; self.machines.get(&name).and_then(|x| x.control_stream())
{
self.poller
.modify(control_socket, Event::readable(event.key))?;
} }
} }
EventTarget::RPCConnection(rpc_connection_id) if self.connections.get(rpc_connection_id).map(Option::is_some).unwrap_or(false) => { EventTarget::RpcConnection(rpc_connection_id)
let (still_open, mut commands) = if let Some(rpc_connection) = &mut self.connections[rpc_connection_id] { if self
.connections
.get(rpc_connection_id)
.map(Option::is_some)
.unwrap_or(false) =>
{
let (still_open, mut commands) = if let Some(rpc_connection) =
&mut self.connections[rpc_connection_id]
{
let input_res = rpc_connection.handle_input(rpc_connection_id)?; let input_res = rpc_connection.handle_input(rpc_connection_id)?;
if input_res.0 { if input_res.0 {
self.poller.modify(&rpc_connection.stream, Event::readable(event.key))?; self.poller
.modify(&rpc_connection.stream, Event::readable(event.key))?;
} }
input_res input_res
@ -342,7 +486,7 @@ impl Daemon {
let (stream, address) = match self.rpc_listener.accept() { let (stream, address) = match self.rpc_listener.accept() {
Ok(value) => value, Ok(value) => value,
Err(err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()), Err(err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()),
Err(err) => return Err(err)? Err(err) => return Err(err.into()),
}; };
stream.set_nonblocking(true)?; stream.set_nonblocking(true)?;
@ -351,16 +495,25 @@ impl Daemon {
let ucred = unsafe { let ucred = unsafe {
let mut ucred: libc::ucred = mem::zeroed(); let mut ucred: libc::ucred = mem::zeroed();
let mut length = size_of::<libc::ucred>() as u32; let mut length = size_of::<libc::ucred>() as u32;
libc::getsockopt(stream.as_raw_fd(), libc::SOL_SOCKET, libc::SO_PEERCRED, (&mut ucred) as *mut _ as _, &mut length); libc::getsockopt(
stream.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERCRED,
(&mut ucred) as *mut _ as _,
&mut length,
);
let passwd = libc::getpwuid(ucred.uid); let passwd = libc::getpwuid(ucred.uid);
if !passwd.is_null() { if !passwd.is_null() {
user = CStr::from_ptr((*passwd).pw_name).to_str().ok().map(|x| x.to_string()) user = CStr::from_ptr((*passwd).pw_name)
.to_str()
.ok()
.map(|x| x.to_string())
} }
ucred ucred
}; };
let conn = RPCConnection { let conn = RpcConnection {
stream, stream,
address, address,
buffer: vec![], buffer: vec![],
@ -371,24 +524,35 @@ impl Daemon {
log::info!( log::info!(
"Got new RPC connection from {} (pid: {}, socket: {:?})", "Got new RPC connection from {} (pid: {}, socket: {:?})",
conn.user.as_ref().map_or_else(|| format!("uid:{}", conn.uid), |x| format!("{} ({})", x, conn.uid)), conn.user.as_ref().map_or_else(
|| format!("uid:{}", conn.uid),
|x| format!("{} ({})", x, conn.uid),
),
conn.pid, conn.pid,
conn.address, conn.address,
); );
let id = self.add_rpc_connection(conn); let id = self.add_rpc_connection(conn);
let event_target = self.add_target(EventTarget::RPCConnection(id)); let event_target = self.add_target(EventTarget::RpcConnection(id));
self.poller.add(&self.connections[id].as_ref().unwrap().stream, Event::readable(event_target))?; self.poller.add(
&self.connections[id].as_ref().unwrap().stream,
Event::readable(event_target),
)?;
} }
} }
pub fn wait(&mut self) -> Result<(), anyhow::Error> { pub fn wait(&mut self) -> Result<(), anyhow::Error> {
self.poller.wait(&mut self.queue, Some(Duration::from_secs(5)))?; self.poller
.wait(&mut self.queue, Some(Duration::from_secs(5)))?;
Ok(()) Ok(())
} }
fn add_target(&mut self, event_target: EventTarget) -> usize { fn add_target(&mut self, event_target: EventTarget) -> usize {
let id = self.event_key_storage.iter().enumerate().find(|(_, target)| target.eq(&&EventTarget::None)); let id = self
.event_key_storage
.iter()
.enumerate()
.find(|(_, target)| target.eq(&&EventTarget::None));
if let Some((id, _)) = id { if let Some((id, _)) = id {
self.event_key_storage[id] = event_target; self.event_key_storage[id] = event_target;
return id; return id;
@ -396,11 +560,15 @@ impl Daemon {
let new_id = self.event_key_storage.len(); let new_id = self.event_key_storage.len();
self.event_key_storage.push(event_target); self.event_key_storage.push(event_target);
return new_id; new_id
} }
fn add_rpc_connection(&mut self, rpc_connection: RPCConnection) -> usize { fn add_rpc_connection(&mut self, rpc_connection: RpcConnection) -> usize {
let id = self.connections.iter().enumerate().find(|(_, target)| target.is_none()); let id = self
.connections
.iter()
.enumerate()
.find(|(_, target)| target.is_none());
if let Some((id, _)) = id { if let Some((id, _)) = id {
self.connections[id] = Some(rpc_connection); self.connections[id] = Some(rpc_connection);
return id; return id;
@ -408,11 +576,12 @@ impl Daemon {
let new_id = self.connections.len(); let new_id = self.connections.len();
self.connections.push(Some(rpc_connection)); self.connections.push(Some(rpc_connection));
return new_id; new_id
} }
fn mount_machine(&mut self, vm: VirtualMachine) { fn mount_machine(&mut self, vm: VirtualMachine) {
log::info!("Loaded {}", vm.name());
let name = vm.name().to_string(); let name = vm.name().to_string();
self.machines.insert(name.clone(), vm); self.machines.insert(name, vm);
} }
} }

Loading…
Cancel
Save