more vore!!
This commit is contained in:
parent
456587d166
commit
9be5e6b976
10 changed files with 712 additions and 73 deletions
108
Cargo.lock
generated
108
Cargo.lock
generated
|
|
@ -27,6 +27,22 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "beau_collector"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33a143066d3cbd3d32c15b51c39449e50e32a68293d31589fb941d6a9df0df4f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
|
|
@ -70,6 +86,12 @@ dependencies = [
|
|||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.13"
|
||||
|
|
@ -79,6 +101,15 @@ dependencies = [
|
|||
"serde 1.0.125",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
|
|
@ -110,12 +141,27 @@ dependencies = [
|
|||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
|
|
@ -181,6 +227,60 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qapi"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e679d7e201702340604a0afd896a1db3bf1488bb9a2d794523d1fd770bbd744"
|
||||
dependencies = [
|
||||
"log",
|
||||
"qapi-qmp",
|
||||
"qapi-spec",
|
||||
"serde 1.0.125",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qapi-codegen"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81b4abbe2a6fbc026bf9bb9021d181d8da4661c6a3cf9a221f6a36aca2617acd"
|
||||
dependencies = [
|
||||
"qapi-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qapi-parser"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2ee46393e919bb03d7ef4c3d633dbbf7d7196fb02b9928ea9fccae7529db07c"
|
||||
dependencies = [
|
||||
"serde 1.0.125",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qapi-qmp"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ef8ca64a52853030d0eac261c34c87978b65060aee7fe54e01659fb0d238500"
|
||||
dependencies = [
|
||||
"qapi-codegen",
|
||||
"qapi-spec",
|
||||
"serde 1.0.125",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qapi-spec"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b360919a24ea5fc02fa762cb01bd8f43b643fee51c585f763257773b4dc5a9e8"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde 1.0.125",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
|
|
@ -319,18 +419,18 @@ name = "vore-core"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"beau_collector",
|
||||
"config",
|
||||
"kiam",
|
||||
"libc",
|
||||
"mlua",
|
||||
"qapi",
|
||||
"qapi-qmp",
|
||||
"serde 1.0.125",
|
||||
"serde_json",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vore-lua"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "vored"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
[machine]
|
||||
name = "win10"
|
||||
memory = "12G"
|
||||
features = ["uefi", "spice", "scream", "looking-glass"]
|
||||
|
||||
[cpu]
|
||||
amount = 12
|
||||
|
|
@ -13,26 +14,27 @@ path = "/dev/disk/by-id/wwn-0x500a0751f008e09d"
|
|||
preset = "ssd"
|
||||
path = "/dev/disk/by-id/wwn-0x5002538e4038852d"
|
||||
|
||||
[uefi]
|
||||
enabled = true
|
||||
[[disk]]
|
||||
preset = "nvme"
|
||||
path = "/dev/disk/by-id/nvme-eui.6479a74530201073"
|
||||
|
||||
#[[vfio]]
|
||||
#vendor = 0x10de
|
||||
#device = 0x1b80
|
||||
#index = 1
|
||||
#
|
||||
#graphics = true
|
||||
#
|
||||
#[[vfio]]
|
||||
#vendor = 0x10de
|
||||
#device = 0x10f0
|
||||
#index = 1
|
||||
|
||||
[[vfio]]
|
||||
slot = "08:00.0"
|
||||
graphics = true
|
||||
|
||||
[[vfio]]
|
||||
slot = "08:00.1"
|
||||
|
||||
[[vfio]]
|
||||
slot = "0a:00.3"
|
||||
|
||||
[spice]
|
||||
enabled = true
|
||||
|
||||
[scream]
|
||||
enabled = true
|
||||
vendor = 0x1022
|
||||
device = 0x149c
|
||||
addr = "0b:00.3"
|
||||
|
||||
[looking-glass]
|
||||
enabled = true
|
||||
width = 2560
|
||||
height = 1080
|
||||
|
|
@ -45,13 +45,9 @@ end
|
|||
|
||||
vore:set_build_command(function(instance, vm)
|
||||
vm:arg("-rtc", "driftfix=slew")
|
||||
--vm:arg("-mon", "stdio")
|
||||
vm:arg("-no-hpet")
|
||||
vm:arg("-boot", "strict=on")
|
||||
|
||||
vm:arg("-chardev", "socket,id=charmonitor,path=/tmp/qemu.sock,server=on,wait=off")
|
||||
vm:arg("-mon", "chardev=charmonitor,id=monitor,mode=readline")
|
||||
|
||||
if instance.kvm then
|
||||
vm:arg("-enable-kvm")
|
||||
end
|
||||
|
|
@ -61,7 +57,7 @@ vore:set_build_command(function(instance, vm)
|
|||
end
|
||||
|
||||
--this disables the QEMU GUI
|
||||
--vm:arg("-display", "none")
|
||||
vm:arg("-display", "none")
|
||||
|
||||
vm:arg("-no-user-config")
|
||||
--vm:arg("-nodefaults")
|
||||
|
|
@ -101,7 +97,7 @@ vore:set_build_command(function(instance, vm)
|
|||
end
|
||||
|
||||
for _, vfio in ipairs(instance.vfio) do
|
||||
local def = "vfio-pci,host=" .. vfio.slot
|
||||
local def = "vfio-pci,host=" .. vfio.address
|
||||
if vfio.graphics then
|
||||
def = def .. ",x-vga=on"
|
||||
end
|
||||
|
|
@ -146,6 +142,7 @@ end)
|
|||
---@param type string
|
||||
---@return fun(vm: VM, idx: number, disk: Disk): VM
|
||||
function scsi_disk_gen(type)
|
||||
-- see https://blog.christophersmart.com/2019/12/18/kvm-guests-with-emulated-ssd-and-nvme-drives/
|
||||
return function(vm, idx, disk)
|
||||
vm:arg(
|
||||
"-blockdev",
|
||||
|
|
@ -184,4 +181,13 @@ function scsi_disk_gen(type)
|
|||
end
|
||||
|
||||
vore:register_disk_preset("ssd", scsi_disk_gen("ssd"))
|
||||
vore:register_disk_preset("hdd", scsi_disk_gen("hdd"))
|
||||
vore:register_disk_preset("hdd", scsi_disk_gen("hdd"))
|
||||
vore:register_disk_preset("nvme", function(vm, _, disk)
|
||||
local nvme_id = vm:get_counter("nvme", 1)
|
||||
|
||||
-- see https://blog.christophersmart.com/2019/12/18/kvm-guests-with-emulated-ssd-and-nvme-drives/
|
||||
vm:arg("-drive", "file=" .. disk.path .. ",driver=raw,if=none,id=NVME" .. nvme_id)
|
||||
vm:arg("-device", "nvme,drive=NVME" .. nvme_id .. ",serial=nvme-" .. nvme_id)
|
||||
|
||||
return vm
|
||||
end)
|
||||
|
|
@ -72,7 +72,10 @@ end
|
|||
---@field buffer_size number
|
||||
|
||||
---@class Vfio
|
||||
---@field slot string
|
||||
---@field device number|nil
|
||||
---@field vendor number|nil
|
||||
---@field index number|nil
|
||||
---@field address string
|
||||
---@field graphics boolean
|
||||
---@field multifunction boolean
|
||||
|
||||
|
|
|
|||
|
|
@ -13,4 +13,8 @@ serde_json = "1.0.64"
|
|||
toml = "*"
|
||||
anyhow = "1.0.40"
|
||||
kiam = "0.1"
|
||||
mlua = { version = "0.5.3", features = ["lua54", "serialize", "send"] }
|
||||
mlua = { 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"] }
|
||||
libc = "0.2.94"
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
use anyhow::{Context, Error};
|
||||
use config::{Config, File, FileFormat, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::de::Visitor;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
|
|
@ -71,17 +73,27 @@ impl InstanceConfig {
|
|||
}
|
||||
}
|
||||
|
||||
if let Ok(looking_glass) = config.get_table("looking-glass") {
|
||||
instance_config.looking_glass =
|
||||
LookingGlassConfig::from_table(looking_glass, &instance_config.name)?;
|
||||
}
|
||||
instance_config.looking_glass = LookingGlassConfig::from_table(
|
||||
config.get_table("looking-glass").unwrap_or_default(),
|
||||
&instance_config.name,
|
||||
)?;
|
||||
instance_config.scream = ScreamConfig::from_table(
|
||||
config.get_table("scream").unwrap_or_default(),
|
||||
&instance_config.name,
|
||||
)?;
|
||||
instance_config.spice =
|
||||
SpiceConfig::from_table(config.get_table("spice").unwrap_or_default())?;
|
||||
|
||||
if let Ok(scream) = config.get_table("scream") {
|
||||
instance_config.scream = ScreamConfig::from_table(scream, &instance_config.name)?;
|
||||
}
|
||||
|
||||
if let Ok(scream) = config.get_table("spice") {
|
||||
instance_config.spice = SpiceConfig::from_table(scream)?;
|
||||
if let Ok(features) = config.get::<Vec<String>>("machine.features") {
|
||||
for feature in features {
|
||||
match feature.as_str() {
|
||||
"looking-glass" => instance_config.looking_glass.enabled = true,
|
||||
"spice" => instance_config.spice.enabled = true,
|
||||
"scream" => instance_config.scream.enabled = true,
|
||||
"uefi" => instance_config.uefi.enabled = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(instance_config)
|
||||
|
|
@ -432,7 +444,7 @@ impl DiskConfig {
|
|||
path,
|
||||
};
|
||||
|
||||
// TODO: Add blockdev details
|
||||
// TODO: Add block dev details
|
||||
|
||||
Ok(disk)
|
||||
}
|
||||
|
|
@ -440,20 +452,141 @@ impl DiskConfig {
|
|||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct VfioConfig {
|
||||
pub slot: String,
|
||||
pub address: PCIAddress,
|
||||
pub vendor: Option<u32>,
|
||||
pub device: Option<u32>,
|
||||
pub index: u32,
|
||||
pub graphics: bool,
|
||||
pub multifunction: bool,
|
||||
}
|
||||
|
||||
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!(
|
||||
"Failed to read the device id of PCI device at {:#} ({})",
|
||||
addr,
|
||||
format!("/sys/bus/pci/devices/{:#}/device", addr)
|
||||
)
|
||||
})?;
|
||||
let found_device = u32::from_str_radix(device.trim_start_matches("0x").trim_end(), 16)?;
|
||||
|
||||
let vendor = std::fs::read_to_string(format!("/sys/bus/pci/devices/{:#}/vendor", addr))
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to read the vendor id of PCI device at {:#} ({})",
|
||||
addr,
|
||||
format!("/sys/bus/pci/devices/{:#}/vendor", addr)
|
||||
)
|
||||
})?;
|
||||
let found_vendor = u32::from_str_radix(vendor.trim_start_matches("0x").trim_end(), 16)?;
|
||||
|
||||
Ok((found_vendor, found_device))
|
||||
}
|
||||
|
||||
impl VfioConfig {
|
||||
pub fn from_table(table: HashMap<String, Value>) -> Result<VfioConfig, anyhow::Error> {
|
||||
let slot = table
|
||||
.get("slot")
|
||||
let mut address = table
|
||||
.get("addr")
|
||||
.or_else(|| table.get("address"))
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow::anyhow!("vfio table needs a slot"))?
|
||||
.into_str()?;
|
||||
.map(|x| PCIAddress::from_str(&x.into_str()?))
|
||||
.transpose()?;
|
||||
|
||||
let vendor = table
|
||||
.get("vendor")
|
||||
.cloned()
|
||||
.map(|x| x.into_int().map(|x| x as u32))
|
||||
.transpose()?;
|
||||
let device = table
|
||||
.get("device")
|
||||
.cloned()
|
||||
.map(|x| x.into_int().map(|x| x as u32))
|
||||
.transpose()?;
|
||||
let index = table
|
||||
.get("index")
|
||||
.cloned()
|
||||
.map(|x| x.into_int().map(|x| x as u32))
|
||||
.transpose()?
|
||||
.unwrap_or(0);
|
||||
|
||||
let address = match (address, vendor, device) {
|
||||
(Some(addr), vendor, device) => {
|
||||
let (found_vendor, found_device) = read_pci_ids(&addr)?;
|
||||
|
||||
if let Some(device) = device {
|
||||
if device != found_device {
|
||||
anyhow::bail!(
|
||||
"VFIO expects a PCI device on address {} with the device id {:#04x} but found the id {:#04x} instead",
|
||||
addr,
|
||||
device,
|
||||
found_device
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vendor) = vendor {
|
||||
if vendor != found_vendor {
|
||||
anyhow::bail!(
|
||||
"VFIO expects a PCI device on address {} with the vendor id {:#04x} but found the id {:#04x} instead",
|
||||
addr,
|
||||
vendor,
|
||||
found_vendor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
addr
|
||||
}
|
||||
|
||||
(None, Some(vendor), Some(device)) => {
|
||||
let mut counter = index;
|
||||
let mut items: Vec<(PCIAddress, u32, u32)> = vec![];
|
||||
|
||||
for entry in std::fs::read_dir("/sys/bus/pci/devices")? {
|
||||
let entry = entry?;
|
||||
let file_name = entry.file_name();
|
||||
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 (found_vendor, found_device) = read_pci_ids(&addr)?;
|
||||
items.push((addr, found_vendor, found_device));
|
||||
}
|
||||
|
||||
items.sort_by_key(|&(addr, _, _)| addr);
|
||||
|
||||
for (addr, found_vendor, found_device) in items {
|
||||
if found_vendor == vendor && found_device == device {
|
||||
if counter == 0 {
|
||||
address = Some(addr);
|
||||
break;
|
||||
}
|
||||
|
||||
counter -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(address) = address {
|
||||
address
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Can't find {}th PCI device with vendor id {:#04x} and device id {:#04x}",
|
||||
index + 1,
|
||||
vendor,
|
||||
device
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_ => anyhow::bail!("VFIO element needs either vendor and device or address to be set"),
|
||||
};
|
||||
|
||||
let mut cfg = VfioConfig {
|
||||
slot,
|
||||
address,
|
||||
vendor: None,
|
||||
device: None,
|
||||
index: 0,
|
||||
graphics: false,
|
||||
multifunction: false,
|
||||
};
|
||||
|
|
@ -490,3 +623,134 @@ impl SpiceConfig {
|
|||
Ok(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct PCIAddress {
|
||||
domain: u32,
|
||||
bus: u8,
|
||||
slot: u8,
|
||||
func: u8,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PCIAddress {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct X;
|
||||
impl Visitor<'_> for X {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
formatter.write_str("Expecting a string")
|
||||
}
|
||||
|
||||
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
let x = deserializer.deserialize_string(X)?;
|
||||
Ok(PCIAddress::from_str(&x).map_err(|x| de::Error::custom(x))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PCIAddress {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl PCIAddress {
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"{:04x}:{:02x}:{:02x}.{:x}",
|
||||
self.domain, self.bus, self.slot, self.func
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for PCIAddress {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("PCIAddress(")?;
|
||||
if f.alternate() && self.domain == 0 {
|
||||
f.write_str(&format!("{:04x}:", self.domain))?;
|
||||
}
|
||||
|
||||
f.write_str(&format!(
|
||||
"{:02x}:{:02x}.{:x}",
|
||||
self.bus, self.slot, self.func
|
||||
))?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
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))?;
|
||||
}
|
||||
|
||||
f.write_str(&format!(
|
||||
"{:02x}:{:02x}.{:x}",
|
||||
self.bus, self.slot, self.func
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if let Some(slot_and_func) = rev.next() {
|
||||
let mut splitter = slot_and_func.split(".");
|
||||
|
||||
if let Some(slot) = splitter.next() {
|
||||
addr.slot = u8::from_str_radix(slot, 16)?;
|
||||
}
|
||||
|
||||
if let Some(func) = splitter.next() {
|
||||
addr.func = u8::from_str_radix(func, 16)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bus) = rev.next() {
|
||||
addr.bus = u8::from_str_radix(bus, 16)?;
|
||||
}
|
||||
|
||||
if let Some(domain) = rev.next() {
|
||||
addr.domain = u32::from_str_radix(domain, 16)?;
|
||||
}
|
||||
|
||||
Ok(addr)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::PCIAddress;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_input_and_output_are_same() {
|
||||
assert_eq!(
|
||||
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")
|
||||
.expect("Failed to parse correct string")
|
||||
.to_string(),
|
||||
"0000:00:01.0"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ mod virtual_machine;
|
|||
pub use global_config::*;
|
||||
pub use instance_config::*;
|
||||
pub use qemu::QemuCommandBuilder;
|
||||
pub use virtual_machine::*;
|
||||
|
|
|
|||
|
|
@ -259,6 +259,8 @@ impl QemuCommandBuilder {
|
|||
let item = VM::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() };
|
||||
|
||||
let build_command = if let Some(build_command) = &self
|
||||
.storage
|
||||
.0
|
||||
|
|
@ -277,9 +279,32 @@ impl QemuCommandBuilder {
|
|||
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 working_dir = working_dir
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Can't change working directory into string"))?;
|
||||
|
||||
// Control socket
|
||||
cmd.push("-chardev".to_string());
|
||||
cmd.push(format!(
|
||||
"socket,id=charmonitor,path={}/qemu.sock,server=on,wait=off",
|
||||
working_dir
|
||||
));
|
||||
|
||||
// Set mode to control so we use qapi/qmp instead of readline mode
|
||||
cmd.push("-mon".to_string());
|
||||
cmd.push("chardev=charmonitor,id=monitor,mode=control".to_string());
|
||||
|
||||
cmd.append(&mut vm_instance.args);
|
||||
|
||||
self.lua.globals().raw_remove("vore")?;
|
||||
|
|
|
|||
|
|
@ -1,38 +1,285 @@
|
|||
use crate::{GlobalConfig, InstanceConfig, QemuCommandBuilder};
|
||||
use anyhow::{Context, Error};
|
||||
use beau_collector::BeauCollector;
|
||||
use qapi::qmp::QMP;
|
||||
use qapi::Qmp;
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fs::{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;
|
||||
use std::process::{Child, Command};
|
||||
use std::result::Result::Ok;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct VirtualMachine {
|
||||
pub struct VirtualMachine {
|
||||
working_dir: PathBuf,
|
||||
config: InstanceConfig,
|
||||
global_config: GlobalConfig,
|
||||
process: Option<Child>,
|
||||
control_socket: Option<ControlSocket>,
|
||||
}
|
||||
|
||||
struct ControlSocket {
|
||||
unix_stream: CloneableUnixStream,
|
||||
qmp: Qmp<qapi::Stream<BufReader<CloneableUnixStream>, CloneableUnixStream>>,
|
||||
info: QMP,
|
||||
}
|
||||
|
||||
impl Debug for ControlSocket {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("ControlSocket")
|
||||
.field(&self.unix_stream)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
const AUTO_UNBIND_BLACKLIST: &[&str] = &["nvidia"];
|
||||
|
||||
impl VirtualMachine {
|
||||
pub fn new(config: InstanceConfig, working_dir: PathBuf) -> VirtualMachine {
|
||||
pub fn new(
|
||||
config: InstanceConfig,
|
||||
global_config: &GlobalConfig,
|
||||
working_dir: PathBuf,
|
||||
) -> VirtualMachine {
|
||||
VirtualMachine {
|
||||
working_dir,
|
||||
config,
|
||||
global_config: global_config.clone(),
|
||||
process: None,
|
||||
control_socket: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, global_config: &GlobalConfig) -> Result<(), anyhow::Error> {
|
||||
pub fn prepare(&mut self, execute_fixes: bool, force: bool) -> Result<(), anyhow::Error> {
|
||||
let mut results = vec![];
|
||||
results.extend(self.prepare_disks());
|
||||
results.extend(self.prepare_vfio(execute_fixes, force));
|
||||
results
|
||||
.into_iter()
|
||||
.bcollect::<()>()
|
||||
.with_context(|| format!("Failed to prepare VM {}", self.config.name))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Doesn't really prepare them, but mostly checks if the user has permissions to read them
|
||||
///
|
||||
pub fn prepare_disks(&self) -> Vec<Result<(), anyhow::Error>> {
|
||||
self.config
|
||||
.disks
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&disk.path)
|
||||
.with_context(|| format!("Failed to open disk {}", disk.path))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Prepare VFIO related shenanigans,
|
||||
/// This includes if requested via [execute_fixes] unbinding the requested vfio pci devices
|
||||
/// And binding them to vfio-pci
|
||||
///
|
||||
/// With [execute_fixes] set to false, it will only check if everything is sane, and the correct driver is loaded
|
||||
fn prepare_vfio(&mut self, execute_fixes: bool, force: bool) -> Vec<Result<(), Error>> {
|
||||
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!(
|
||||
"Path to device driver for PCI device at {} doesn't have a path to a driver",
|
||||
vfio.address
|
||||
)
|
||||
})?;
|
||||
|
||||
driver.to_string()
|
||||
}
|
||||
|
||||
Err(err) if err.kind() == ErrorKind::NotFound => "".to_string(),
|
||||
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
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 {
|
||||
let address = format!("{:#}\n", vfio.address).into_bytes();
|
||||
|
||||
if !driver.is_empty() {
|
||||
let mut unbind = std::fs::OpenOptions::new().append(true).open(format!(
|
||||
"/sys/bus/pci/devices/{:#}/driver/unbind",
|
||||
vfio.address
|
||||
))?;
|
||||
|
||||
unbind.write_all(&address)?;
|
||||
}
|
||||
|
||||
{
|
||||
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")?;
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn get_cmd_line(&self) -> Result<Vec<String>, anyhow::Error> {
|
||||
let builder = QemuCommandBuilder::new(&self.global_config, self.working_dir.clone())?;
|
||||
builder.build(&self.config)
|
||||
}
|
||||
|
||||
pub fn pin_qemu_threads(&self) {
|
||||
let pid = if let Some(child) = &self.process {
|
||||
child.id()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> Result<(), anyhow::Error> {
|
||||
if let Some(proc) = &mut self.process {
|
||||
if proc.try_wait()?.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let builder = QemuCommandBuilder::new(global_config, self.working_dir.clone())?;
|
||||
let cmd = builder.build(&self.config)?;
|
||||
|
||||
let mut command = Command::new("qemu-system-x86_64");
|
||||
command.args(cmd);
|
||||
command.args(self.get_cmd_line()?);
|
||||
self.process = Some(command.spawn()?);
|
||||
|
||||
Ok(())
|
||||
let mut res = || {
|
||||
let qemu_control_socket = format!("{}/qemu.sock", self.working_dir.to_str().unwrap());
|
||||
|
||||
let mut unix_stream = UnixStream::connect(&qemu_control_socket);
|
||||
let mut time = 30;
|
||||
while let Err(err) = unix_stream {
|
||||
if time < 0 {
|
||||
Err(err).context(format!(
|
||||
"After 30 seconds, QEMU Control socket ({}) didn't come up",
|
||||
qemu_control_socket
|
||||
))?;
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
unix_stream = UnixStream::connect(&qemu_control_socket);
|
||||
time -= 1;
|
||||
}
|
||||
|
||||
let unix_stream = unix_stream.unwrap();
|
||||
let unix_stream = CloneableUnixStream(Arc::new(Mutex::new(unix_stream)));
|
||||
let mut qmp = Qmp::from_stream(unix_stream.clone());
|
||||
|
||||
let handshake = qmp.handshake()?;
|
||||
|
||||
let mut control_socket = ControlSocket {
|
||||
unix_stream,
|
||||
qmp,
|
||||
info: handshake,
|
||||
};
|
||||
|
||||
self.pin_qemu_threads();
|
||||
|
||||
control_socket
|
||||
.qmp
|
||||
.execute(&qapi_qmp::cont {})
|
||||
.context("Failed to send start command on qemu control socket")?;
|
||||
|
||||
control_socket.qmp.nop()?;
|
||||
|
||||
while let Some(event) = control_socket.qmp.events().next() {
|
||||
println!("event: {:?}", event);
|
||||
}
|
||||
|
||||
self.control_socket = Some(control_socket);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let result_ = res();
|
||||
if result_.is_err() {
|
||||
if let Some(mut qemu) = self.process.take() {
|
||||
qemu.kill()?;
|
||||
qemu.wait()?;
|
||||
}
|
||||
}
|
||||
|
||||
result_
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CloneableUnixStream(Arc<Mutex<UnixStream>>);
|
||||
|
||||
impl CloneableUnixStream {
|
||||
pub fn lock(&self) -> Result<MutexGuard<'_, UnixStream>, std::io::Error> {
|
||||
self.0.lock().map_err(|_| {
|
||||
io::Error::new(
|
||||
ErrorKind::Other,
|
||||
anyhow::anyhow!("Failed to lock UnixStream"),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for CloneableUnixStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let res = self.lock()?.read(buf);
|
||||
if let Ok(size) = res {
|
||||
println!("READ: {}", String::from_utf8_lossy(&buf[..size]));
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for CloneableUnixStream {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.lock()?.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.lock()?.flush()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,10 @@
|
|||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use vore_core::{GlobalConfig, InstanceConfig, QemuCommandBuilder};
|
||||
use vore_core::{GlobalConfig, InstanceConfig, VirtualMachine};
|
||||
|
||||
fn main() {
|
||||
let cfg = InstanceConfig::from_toml(include_str!("../../config/example.toml")).unwrap();
|
||||
println!("CONFIG:\n{:#?}", cfg);
|
||||
let global = GlobalConfig::load(include_str!("../../config/global.toml")).unwrap();
|
||||
let builder =
|
||||
QemuCommandBuilder::new(&global, PathBuf::from("/home/eater/.lib/vore/win10")).unwrap();
|
||||
let command = builder.build(&cfg).unwrap();
|
||||
// .iter()
|
||||
// .map(|x| format!("'{}'", x))
|
||||
// .collect::<Vec<_>>()
|
||||
// .join(" ");
|
||||
|
||||
Command::new("qemu-system-x86_64")
|
||||
.args(command)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
let mut vm = VirtualMachine::new(cfg, &global, PathBuf::from("/home/eater/.local/vore/win10"));
|
||||
vm.prepare(true, false).unwrap();
|
||||
vm.start().unwrap();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue