You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
11 KiB
Rust
335 lines
11 KiB
Rust
use crate::{GlobalConfig, InstanceConfig};
|
|
use anyhow::Context;
|
|
use mlua::prelude::LuaError;
|
|
use mlua::{
|
|
Function, Lua, LuaSerdeExt, MultiValue, RegistryKey, Table, ToLua, UserData, UserDataMethods,
|
|
Value,
|
|
};
|
|
use serde::ser::Error;
|
|
use serde::Deserialize;
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use std::sync::{Arc, Mutex, Weak};
|
|
|
|
#[derive(Debug, Default, Deserialize, Clone)]
|
|
struct VM {
|
|
args: Vec<String>,
|
|
bus_ids: HashMap<String, usize>,
|
|
devices: HashMap<String, String>,
|
|
device: bool,
|
|
}
|
|
|
|
impl UserData for VM {
|
|
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(",");
|
|
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());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.device = false;
|
|
}
|
|
|
|
if item == "-device" {
|
|
this.device = true;
|
|
}
|
|
|
|
this.args.push(item)
|
|
}
|
|
}
|
|
|
|
Ok(Value::Nil)
|
|
});
|
|
|
|
methods.add_method("get_device_id", |lua, this, _type: String| {
|
|
this.devices
|
|
.get(&_type)
|
|
.map_or(Ok(Value::Nil), |x| x.as_str().to_lua(lua))
|
|
});
|
|
|
|
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)
|
|
});
|
|
|
|
methods.add_method_mut("get_counter", |lua, this, args: (String, usize)| {
|
|
let (name, start) = args;
|
|
|
|
this.bus_ids
|
|
.entry(name)
|
|
.and_modify(|x| *x += 1)
|
|
.or_insert(start)
|
|
.to_lua(lua)
|
|
});
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct VoreLuaStorage(Arc<Mutex<VoreLuaStorageInner>>);
|
|
|
|
impl VoreLuaStorage {
|
|
pub fn weak(&self) -> VoreLuaWeakStorage {
|
|
VoreLuaWeakStorage(Arc::downgrade(&self.0))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct VoreLuaWeakStorage(Weak<Mutex<VoreLuaStorageInner>>);
|
|
|
|
#[derive(Debug)]
|
|
pub struct VoreLuaStorageInner {
|
|
build_command: Option<RegistryKey>,
|
|
disk_presets: HashMap<String, RegistryKey>,
|
|
working_dir: PathBuf,
|
|
}
|
|
|
|
impl UserData for VoreLuaWeakStorage {
|
|
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
|
methods.add_method("set_build_command", |l, weak, func: Function| {
|
|
let strong = weak
|
|
.0
|
|
.upgrade()
|
|
.ok_or(LuaError::custom("vore storage has expired"))?;
|
|
let mut this = strong
|
|
.try_lock()
|
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
|
|
|
if let Some(reg) = this.build_command.take() {
|
|
l.remove_registry_value(reg)?;
|
|
}
|
|
|
|
this.build_command = Some(l.create_registry_value(func)?);
|
|
Ok(Value::Nil)
|
|
});
|
|
|
|
methods.add_method(
|
|
"register_disk_preset",
|
|
|lua, weak, args: (mlua::String, Function)| {
|
|
let strong = weak
|
|
.0
|
|
.upgrade()
|
|
.ok_or(LuaError::custom("vore storage has expired"))?;
|
|
let mut this = strong
|
|
.try_lock()
|
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
|
let key = lua.create_registry_value(args.1)?;
|
|
|
|
if let Some(old) = this.disk_presets.insert(args.0.to_str()?.to_string(), key) {
|
|
lua.remove_registry_value(old)?;
|
|
}
|
|
|
|
Ok(Value::Nil)
|
|
},
|
|
);
|
|
|
|
methods.add_method("get_file", |lua, weak, args: (String, String)| {
|
|
let (target, source) = args;
|
|
let strong = weak
|
|
.0
|
|
.upgrade()
|
|
.ok_or(LuaError::custom("vore storage has expired"))?;
|
|
let this = strong
|
|
.try_lock()
|
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
|
|
|
let target = this.working_dir.join(target);
|
|
if !target.exists() {
|
|
if let Some(parent) = target.parent() {
|
|
if !parent.is_file() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
}
|
|
std::fs::copy(source, &target)?;
|
|
}
|
|
|
|
let path_str = target
|
|
.to_str()
|
|
.ok_or_else(|| LuaError::custom("Path can't be made into string"))?;
|
|
path_str.to_lua(lua)
|
|
});
|
|
|
|
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;
|
|
let function = {
|
|
let strong = weak
|
|
.0
|
|
.upgrade()
|
|
.ok_or(LuaError::custom("vore storage has expired"))?;
|
|
let this = strong
|
|
.try_lock()
|
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?;
|
|
|
|
let preset_name = disk
|
|
.get::<&str, String>("preset")
|
|
.with_context(|| format!("Disk {} has no preset", index))
|
|
.map_err(LuaError::external)?;
|
|
|
|
let key = this
|
|
.disk_presets
|
|
.get(&preset_name)
|
|
.clone()
|
|
.with_context(|| {
|
|
format!("No disk preset with the name '{}' found", preset_name)
|
|
})
|
|
.map_err(LuaError::external)?;
|
|
|
|
lua.registry_value::<Function>(key)?
|
|
};
|
|
|
|
function.call((vm, instance, index, disk))
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
impl VoreLuaStorage {
|
|
pub fn new(working_dir: PathBuf) -> VoreLuaStorage {
|
|
VoreLuaStorage(Arc::new(Mutex::new(VoreLuaStorageInner {
|
|
build_command: None,
|
|
disk_presets: Default::default(),
|
|
working_dir,
|
|
})))
|
|
}
|
|
}
|
|
|
|
pub struct QemuCommandBuilder {
|
|
lua: Lua,
|
|
storage: VoreLuaStorage,
|
|
}
|
|
|
|
impl QemuCommandBuilder {
|
|
pub fn new(
|
|
global: &GlobalConfig,
|
|
working_dir: PathBuf,
|
|
) -> Result<QemuCommandBuilder, anyhow::Error> {
|
|
let builder = QemuCommandBuilder {
|
|
lua: Lua::new(),
|
|
storage: VoreLuaStorage::new(working_dir),
|
|
};
|
|
|
|
builder.init(global)?;
|
|
Ok(builder)
|
|
}
|
|
|
|
fn init(&self, global: &GlobalConfig) -> Result<(), anyhow::Error> {
|
|
let globals = self.lua.globals();
|
|
|
|
globals.set(
|
|
"tojson",
|
|
self.lua.create_function(|lua, value: Value| {
|
|
let x = serde_json::to_string(&value)
|
|
.context("Failed transforming value into JSON")
|
|
.map_err(LuaError::external)?;
|
|
lua.create_string(&x)
|
|
})?,
|
|
)?;
|
|
|
|
globals.set("vore", self.storage.weak())?;
|
|
globals.set("global", self.lua.to_value(global)?)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn build(self, config: &InstanceConfig) -> Result<Vec<String>, anyhow::Error> {
|
|
// TODO: load correct script
|
|
self.lua
|
|
.load(include_str!("../../config/qemu.lua"))
|
|
.eval::<()>()
|
|
.context("Failed to run the configured qemu lua script")?;
|
|
|
|
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
|
|
.lock()
|
|
.map_err(|_| LuaError::custom("Failed to lock vore storage"))?
|
|
.build_command
|
|
{
|
|
self.lua.registry_value::<Function>(build_command)?
|
|
} else {
|
|
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 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")?;
|
|
|
|
self.lua.gc_collect()?;
|
|
|
|
if Arc::strong_count(&self.storage.0) > 1 {
|
|
anyhow::bail!("Something still owns vore, can't continue");
|
|
}
|
|
|
|
let x = Arc::try_unwrap(self.storage.0)
|
|
.map_err(|_| anyhow::anyhow!("Something still owns vore, can't continue"))?;
|
|
let storage: VoreLuaStorageInner = x
|
|
.into_inner()
|
|
.map_err(|_| anyhow::anyhow!("Something still owns vore, can't continue"))?;
|
|
|
|
self.lua
|
|
.remove_registry_value(storage.build_command.unwrap())?;
|
|
for (_, item) in storage.disk_presets.into_iter() {
|
|
self.lua.remove_registry_value(item)?;
|
|
}
|
|
|
|
self.lua.gc_collect()?;
|
|
|
|
Ok(cmd)
|
|
}
|
|
}
|