release when??

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

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

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

@ -126,7 +126,7 @@ vore:set_build_command(function(instance, vm)
end
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")
end

@ -4,7 +4,10 @@ version = "0.1.0"
authors = ["eater <=@eater.me>"]
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]
config = { version = "0.11.0", default-features = false, features = ["toml"] }
@ -13,10 +16,10 @@ serde_json = "1.0.64"
toml = "*"
anyhow = "1.0.40"
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"
qapi-qmp = "0.7.0"
qapi = { version = "0.7.0", features = ["qapi-qmp"] }
qapi-qmp = { optional = true, version = "0.7.0" }
qapi = { optional = true, version = "0.7.0", features = ["qapi-qmp"] }
libc = "0.2.94"
lazy_static = "1.4.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> {
if cfg!(target_os = "linux") {
return crate::cpu_list::linux::get_cpus();
crate::cpu_list::linux::get_cpus()
} else {
unimplemented!();
}
@ -32,7 +32,7 @@ pub struct CpuList {
impl CpuList {
pub fn _get() -> CpuList {
return *CPU_LIST;
*CPU_LIST
}
pub fn _amount() -> usize {

@ -1,15 +1,69 @@
use anyhow::Context;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub const GLOBAL_CONFIG_LOCATION: &str = "/home/eater/projects/vored/config/global.toml";
use std::ffi::CString;
use std::fs;
use std::fs::Permissions;
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GlobalConfig {
pub vore: GlobalVoreConfig,
pub qemu: GlobalQemuConfig,
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)]
pub struct GlobalQemuConfig {
pub script: String,

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

@ -4,18 +4,22 @@ mod qemu;
mod virtual_machine;
mod cpu_list;
pub mod rpc;
pub mod consts;
mod virtual_machine_info;
pub use global_config::*;
pub use instance_config::*;
pub use qemu::QemuCommandBuilder;
#[cfg(feature = "host")]
pub use virtual_machine::*;
use log::LevelFilter;
pub use virtual_machine_info::*;
pub fn init_logging() {
let mut builder = pretty_env_logger::formatted_timed_builder();
#[cfg(debug_assertions)] {
use log::LevelFilter;
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();
}

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

@ -9,13 +9,13 @@ macro_rules! define_requests {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "query", rename_all = "snake_case")]
pub enum AllRequests {
$($name(paste! { [<$name Request >] })),+
$($name(Box<paste! { [<$name Request >] }>)),+
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "answer", rename_all = "snake_case")]
pub enum AllResponses {
$($name(paste! { [<$name Response >] })),+
$($name(Box<paste! { [<$name Response >] }>)),+
}
$(
@ -29,13 +29,13 @@ macro_rules! define_requests {
type Response = [<$name Response>];
fn into_enum(self) -> AllRequests {
AllRequests::$name(self)
AllRequests::$name(Box::new(self))
}
}
impl Response for [<$name Response>] {
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)?;
str.push('\n');
return Ok(str);
Ok(str)
}
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 beau_collector::BeauCollector;
use qapi::qmp::{QMP, Event};
use qapi::{Qmp};
use std::{fmt, mem};
use std::fmt::{Debug, Formatter, Display};
use std::fs::{read_link, OpenOptions, read_dir};
use libc::{cpu_set_t, sched_setaffinity, CPU_SET};
use qapi::qmp::{Event, QMP};
use qapi::Qmp;
use qapi_qmp::QmpCommand;
use std::fmt::{Debug, Formatter};
use std::fs::{read_dir, read_link, OpenOptions};
use std::io;
use std::io::{BufReader, ErrorKind, Read, Write};
use std::option::Option::Some;
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::result::Result::Ok;
use std::slice::Iter;
use std::str::FromStr;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, Instant};
use qapi_qmp::QmpCommand;
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,
}
use std::{fmt, mem};
#[derive(Debug)]
pub struct VirtualMachine {
@ -60,6 +35,7 @@ pub struct VirtualMachine {
global_config: GlobalConfig,
process: Option<Child>,
control_socket: Option<ControlSocket>,
quit_after_shutdown: bool,
}
struct ControlSocket {
@ -91,9 +67,14 @@ impl VirtualMachine {
global_config: global_config.clone(),
process: None,
control_socket: None,
quit_after_shutdown: true,
}
}
pub fn vfio_devices(&self) -> Iter<'_, VfioConfig> {
self.config.vfio.iter()
}
pub fn name(&self) -> &str {
&self.config.name
}
@ -104,6 +85,7 @@ impl VirtualMachine {
working_dir: self.working_dir.clone(),
config: self.config.clone(),
state: self.state,
quit_after_shutdown: self.quit_after_shutdown,
}
}
@ -128,7 +110,8 @@ impl VirtualMachine {
let mut shm = vec![];
if self.config.looking_glass.enabled {
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);
@ -142,12 +125,15 @@ impl VirtualMachine {
shm.push(&self.config.scream.mem_path);
}
shm
.into_iter()
shm.into_iter()
.map(|x| Path::new(x))
.filter_map(|x| x.parent())
.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()
}
@ -155,7 +141,12 @@ impl VirtualMachine {
let mut sockets = vec![];
if self.config.spice.enabled {
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);
@ -166,7 +157,11 @@ impl VirtualMachine {
.map(|x| Path::new(x))
.filter_map(|x| x.parent())
.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()
}
@ -214,84 +209,97 @@ impl VirtualMachine {
Ok(_) => {}
}
self.config.vfio.iter().map(|vfio| {
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!(
self.config
.vfio
.iter()
.map(|vfio| VirtualMachine::prepare_vfio_device(execute_fixes, force, vfio))
.collect::<Vec<_>>()
}
pub fn should_auto_start(&self) -> bool {
self.config.auto_start
}
pub fn prepare_vfio_device(
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",
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.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)
} 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)
} 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)
}
if driver != "vfio-pci" && (!execute_fixes || 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)
} 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)
} 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)
}
}
if driver != "vfio-pci" && execute_fixes && !is_blacklisted {
let address = format!("{:#}\n", vfio.address).into_bytes();
if driver != "vfio-pci" && execute_fixes && !is_blacklisted {
let address = format!("{:#}\n", vfio.address).into_bytes();
if !driver.is_empty() {
// Unbind the PCI device from the current driver
let mut unbind = std::fs::OpenOptions::new().append(true).open(format!(
"/sys/bus/pci/devices/{:#}/driver/unbind",
vfio.address
))?;
if !driver.is_empty() {
// Unbind the PCI device from the current driver
let mut unbind = std::fs::OpenOptions::new().append(true).open(format!(
"/sys/bus/pci/devices/{:#}/driver/unbind",
vfio.address
))?;
unbind.write_all(&address)?;
}
unbind.write_all(&address)?;
}
{
// Set a driver override
let mut driver_override = OpenOptions::new().append(true).open(format!(
"/sys/bus/pci/devices/{:#}/driver_override",
vfio.address
))?;
{
// Set a driver override
let mut driver_override = OpenOptions::new().append(true).open(format!(
"/sys/bus/pci/devices/{:#}/driver_override",
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
let mut probe = OpenOptions::new()
.append(true)
.open("/sys/bus/pci/drivers_probe")?;
probe.write_all(&address)?;
}
{
// Probe the PCI device so the driver override is picked up
let mut probe = OpenOptions::new()
.append(true)
.open("/sys/bus/pci/drivers_probe")?;
probe.write_all(&address)?;
}
let new_link = read_link(&pci_driver_path)?;
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)
}
let new_link = read_link(&pci_driver_path)?;
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)
}
}
Ok(())
})
.collect::<Vec<_>>()
Ok(())
}
pub fn get_cmd_line(&self) -> Result<Vec<String>, anyhow::Error> {
@ -321,7 +329,11 @@ impl VirtualMachine {
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() {
continue;
}
@ -330,7 +342,11 @@ impl VirtualMachine {
let name = entry.path().join("comm");
let comm = std::fs::read_to_string(name)?;
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();
kvm_threads.push((tid, cpu_id));
}
@ -358,10 +374,12 @@ impl VirtualMachine {
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() {
// 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<_>>()
@ -370,11 +388,11 @@ impl VirtualMachine {
};
for event in events {
println!("Event: {:?}", event);
log::info!("vm {} got event: {:?}", self.name(), event);
match event {
Event::STOP { .. } => {
if self.state != VirtualMachineState::Stopped {
if self.state == VirtualMachineState::Running {
self.state = VirtualMachineState::Paused;
}
}
@ -383,6 +401,10 @@ impl VirtualMachine {
}
Event::SHUTDOWN { .. } => {
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> {
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(());
}
@ -433,17 +458,32 @@ impl VirtualMachine {
}
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(())
}
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();
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())
.transpose()?
.is_some();
@ -480,7 +520,10 @@ impl VirtualMachine {
}
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()?);
let mut res = || {
@ -499,7 +542,7 @@ impl VirtualMachine {
unix_stream = UnixStream::connect(&qemu_control_socket);
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")
}
}
@ -520,6 +563,18 @@ impl VirtualMachine {
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
.qmp
.execute(&qapi_qmp::cont {})
@ -588,4 +643,4 @@ impl Write for CloneableUnixStream {
fn flush(&mut self) -> io::Result<()> {
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"
required: false
takes_value: true
default_value: "/run/vore.sock"
long: conn
short: c
@ -102,6 +101,7 @@ subcommands:
visible_alias: lg
args:
- vm-name:
long: vm
help: "VM to start looking glass instance for, if not given the ONLY running instance will be used"
required: false
takes_value: true

@ -1,13 +1,14 @@
mod client;
use vore_core::{init_logging, VirtualMachineInfo};
use crate::client::Client;
use clap::{App, ArgMatches};
use std::{fs, mem};
use anyhow::Context;
use clap::{App, ArgMatches};
use std::option::Option::Some;
use std::process::Command;
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() {
init_logging();
@ -21,11 +22,9 @@ fn main_res() -> anyhow::Result<()> {
let yaml = clap::load_yaml!("../clap.yml");
let app: App = App::from(yaml);
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 {
client
};
let mut vore = VoreApp { client };
match matches.subcommand() {
("load", Some(args)) => {
@ -52,17 +51,15 @@ fn main_res() -> anyhow::Result<()> {
vore.looking_glass(args)?;
}
("daemon", Some(args)) => {
match args.subcommand() {
("version", _) => {
vore.daemon_version()?;
}
("daemon", Some(args)) => match args.subcommand() {
("version", _) => {
vore.daemon_version()?;
}
(s, _) => {
log::error!("Subcommand daemon.{} not implemented", s);
}
(s, _) => {
log::error!("Subcommand daemon.{} not implemented", s);
}
}
},
(s, _) => {
log::error!("Subcommand {} not implemented", s);
@ -72,20 +69,22 @@ fn main_res() -> anyhow::Result<()> {
Ok(())
}
struct LoadVMOptions {
struct LoadVirtualMachineOptions {
config: String,
cd_roms: Vec<String>,
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 config = fs::read_to_string(vm_config_path)
.with_context(|| format!("Failed to read vm config at {}", vm_config_path))?;
Ok(LoadVMOptions {
Ok(LoadVirtualMachineOptions {
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"),
})
}
@ -102,11 +101,13 @@ impl VoreApp {
pub fn get_vm(&mut self, args: &ArgMatches) -> anyhow::Result<VirtualMachineInfo> {
let mut items = self.client.list_vms()?;
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))
} else {
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"),
_ => anyhow::bail!("Multiple VM's are loaded, please specify one"),
}
@ -122,7 +123,9 @@ impl VoreApp {
fn load(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
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);
Ok(())
}
@ -139,13 +142,21 @@ impl VoreApp {
fn prepare(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
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(())
}
fn start(&mut self, args: &ArgMatches) -> anyhow::Result<()> {
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(())
}
@ -155,7 +166,9 @@ impl VoreApp {
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 {
command.args(&["-c", &vm.config.spice.socket_path, "-p", "0"]);
} else {
@ -163,7 +176,10 @@ impl VoreApp {
}
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);
command.exec();

@ -8,7 +8,7 @@ edition = "2018"
[dependencies]
anyhow = "1.0.40"
vore-core = { path = "../vore-core" }
vore-core = { path = "../vore-core", features = ["host"] }
polling = "2.0.3"
log = "0.4.14"
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 std::{mem, io};
use std::io::{Read, Write};
use vore_core::rpc::{CommandCenter, Response, Command, AllRequests, AllResponses};
use vore_core::rpc;
use signal_hook::low_level::{signal_name};
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 polling::{Event, Poller};
use signal_hook::consts::{SIGHUP, SIGINT, SIGTERM};
use signal_hook::iterator::{Handle, Signals, SignalsInfo};
use signal_hook::low_level::signal_name;
use std::collections::HashMap;
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::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)]
struct RPCConnection {
struct RpcConnection {
stream: UnixStream,
address: SocketAddr,
buffer: Vec<u8>,
@ -27,7 +30,7 @@ struct RPCConnection {
pid: i32,
}
impl Write for RPCConnection {
impl Write for RpcConnection {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
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> {
self.stream.read(buf)
}
}
#[allow(clippy::char_lit_as_u8)]
const NEWLINE: u8 = '\n' as u8;
impl RPCConnection {
pub fn handle_input(&mut self, own_id: usize) -> Result<(bool, Vec<(usize, Command)>), anyhow::Error> {
impl RpcConnection {
pub fn handle_input(
&mut self,
own_id: usize,
) -> Result<(bool, Vec<(usize, Command)>), anyhow::Error> {
let mut still_open = true;
loop {
let mut buffer = vec![0u8; 4096];
@ -57,13 +64,21 @@ impl RPCConnection {
}
Ok(amount) => self.buffer.extend_from_slice(&buffer[..amount]),
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);
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![];
@ -73,7 +88,6 @@ impl RPCConnection {
continue;
}
let lossy = String::from_utf8_lossy(part);
match CommandCenter::read_command(&lossy) {
@ -94,9 +108,9 @@ impl RPCConnection {
#[derive(Clone, Eq, PartialEq, Debug)]
enum EventTarget {
RPCListener,
RpcListener,
Machine(String),
RPCConnection(usize),
RpcConnection(usize),
None,
}
@ -105,7 +119,7 @@ pub struct Daemon {
event_key_storage: Vec<EventTarget>,
global_config: GlobalConfig,
machines: HashMap<String, VirtualMachine>,
connections: Vec<Option<RPCConnection>>,
connections: Vec<Option<RpcConnection>>,
rpc_listener: UnixListener,
socket_path: PathBuf,
poller: Poller,
@ -117,18 +131,22 @@ pub struct Daemon {
impl Daemon {
pub fn new() -> Result<Daemon, anyhow::Error> {
log::debug!("Loading global config ({})", GLOBAL_CONFIG_LOCATION);
let toml = std::fs::read_to_string(GLOBAL_CONFIG_LOCATION)?;
let global_config = GlobalConfig::load(&toml)?;
log::debug!("Loading global config ({})", VORE_CONFIG);
let toml = std::fs::read_to_string(VORE_CONFIG)?;
let mut global_config = GlobalConfig::load(&toml)?;
log::debug!("Creating vore daemon");
let signals = Signals::new(&[SIGINT, SIGHUP])?;
let handle = signals.handle();
log::debug!("Bound signal handlers");
let poller = Poller::new().context("Failed to make poller")?;
let socket_path = PathBuf::from_str("/run/vore.sock")?;
let rpc_listener = UnixListener::bind(&socket_path).context("Failed to bind vore socket")?;
let socket_path = PathBuf::from_str(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)?;
log::debug!("Bound to /run/vore.sock");
log::debug!("Bound to {}", VORE_SOCKET);
let mut daemon = Daemon {
event_key_storage: vec![],
@ -149,23 +167,106 @@ impl Daemon {
}
pub fn init(&mut self) -> Result<(), anyhow::Error> {
let new_key = self.add_target(EventTarget::RPCListener);
self.poller.add(&self.rpc_listener, Event::readable(new_key))?;
let new_key = self.add_target(EventTarget::RpcListener);
self.poller
.add(&self.rpc_listener, Event::readable(new_key))?;
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> {
self.load_definitions()?;
self.reserve_vfio_devices();
self.auto_start_machines();
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 {
// 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()? {
break;
}
}
err => err?
err => err?,
}
if !self.handle_event_queue()? {
@ -196,37 +297,63 @@ impl Daemon {
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> {
let resp = match &command.data {
AllRequests::Info(_) => {
rpc::InfoResponse {
name: "vore".to_string(),
version: format!("{}.{}.{}{}",
env!("CARGO_PKG_VERSION_MAJOR"),
env!("CARGO_PKG_VERSION_MINOR"),
env!("CARGO_PKG_VERSION_PATCH"),
option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")),
}
.into_enum()
AllRequests::Info(_) => rpc::InfoResponse {
name: "vore".to_string(),
version: format!(
"{}.{}.{}{}",
env!("CARGO_PKG_VERSION_MAJOR"),
env!("CARGO_PKG_VERSION_MINOR"),
env!("CARGO_PKG_VERSION_PATCH"),
option_env!("CARGO_PKG_VERSION_PRE").unwrap_or("")
),
}
AllRequests::List(_) => {
rpc::ListResponse {
items: self.machines.values().map(|x| x.info()).collect()
}
.into_enum()
.into_enum(),
AllRequests::List(_) => rpc::ListResponse {
items: self.machines.values().map(|x| x.info()).collect(),
}
AllRequests::Load(val) => {
let config = InstanceConfig::from_toml(&val.toml)?;
let working_dir = val.working_directory.as_ref().cloned().unwrap_or_else(|| format!("/var/lib/vore/{}", config.name));
let vm = VirtualMachine::new(config, &self.global_config, working_dir);
let info = vm.info();
self.mount_machine(vm);
rpc::LoadResponse {
info,
}
.into_enum()
.into_enum(),
AllRequests::Load(val) => rpc::LoadResponse {
info: self.load_virtual_machine(
&val.toml,
val.working_directory.as_ref().cloned(),
val.save,
)?,
}
.into_enum(),
AllRequests::Prepare(val) => {
if let Some(machine) = self.machines.get_mut(&val.name) {
machine.prepare(true, false)?;
@ -280,7 +407,11 @@ impl Daemon {
pub fn handle_exit_code(&mut self) -> Result<bool, anyhow::Error> {
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 {
SIGINT | SIGTERM => return Ok(false),
_ => {}
@ -297,8 +428,9 @@ impl Daemon {
log::debug!("Handling {:?} from target {:?}", event, item);
match item {
EventTarget::RPCListener => {
self.poller.modify(&self.rpc_listener, Event::readable(event.key))?;
EventTarget::RpcListener => {
self.poller
.modify(&self.rpc_listener, Event::readable(event.key))?;
self.accept_rpc_connections()?;
}
EventTarget::Machine(name) if self.machines.contains_key(&name) => {
@ -306,15 +438,27 @@ impl Daemon {
machine.boop()?;
}
if let Some(control_socket) = self.machines.get(&name).and_then(|x| x.control_stream()) {
self.poller.modify(control_socket, Event::readable(event.key))?;
if let Some(control_socket) =
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) => {
let (still_open, mut commands) = if let Some(rpc_connection) = &mut self.connections[rpc_connection_id] {
EventTarget::RpcConnection(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)?;
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
@ -342,7 +486,7 @@ impl Daemon {
let (stream, address) = match self.rpc_listener.accept() {
Ok(value) => value,
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)?;
@ -351,16 +495,25 @@ impl Daemon {
let ucred = unsafe {
let mut ucred: libc::ucred = mem::zeroed();
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);
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
};
let conn = RPCConnection {
let conn = RpcConnection {
stream,
address,
buffer: vec![],
@ -371,24 +524,35 @@ impl Daemon {
log::info!(
"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.address,
);
let id = self.add_rpc_connection(conn);
let event_target = self.add_target(EventTarget::RPCConnection(id));
self.poller.add(&self.connections[id].as_ref().unwrap().stream, Event::readable(event_target))?;
let event_target = self.add_target(EventTarget::RpcConnection(id));
self.poller.add(
&self.connections[id].as_ref().unwrap().stream,
Event::readable(event_target),
)?;
}
}
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(())
}
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 {
self.event_key_storage[id] = event_target;
return id;
@ -396,11 +560,15 @@ impl Daemon {
let new_id = self.event_key_storage.len();
self.event_key_storage.push(event_target);
return new_id;
new_id
}
fn add_rpc_connection(&mut self, rpc_connection: RPCConnection) -> usize {
let id = self.connections.iter().enumerate().find(|(_, target)| target.is_none());
fn add_rpc_connection(&mut self, rpc_connection: RpcConnection) -> usize {
let id = self
.connections
.iter()
.enumerate()
.find(|(_, target)| target.is_none());
if let Some((id, _)) = id {
self.connections[id] = Some(rpc_connection);
return id;
@ -408,11 +576,12 @@ impl Daemon {
let new_id = self.connections.len();
self.connections.push(Some(rpc_connection));
return new_id;
new_id
}
fn mount_machine(&mut self, vm: VirtualMachine) {
log::info!("Loaded {}", vm.name());
let name = vm.name().to_string();
self.machines.insert(name.clone(), vm);
self.machines.insert(name, vm);
}
}
}

Loading…
Cancel
Save