use std::path::{Path, PathBuf};
use crate::config::{Result, TestbedError, VmProfile};
const NAME_KEY: &str = "Name";
const UTM_VERSION_KEY: &str = "UTMVersion";
fn plist_get(plist_path: &Path, key: &str) -> Result<String> {
let output = std::process::Command::new("/usr/libexec/PlistBuddy")
.args(["-c", &format!("Print :{key}"), plist_path.to_str().unwrap()])
.output()
.map_err(|e| TestbedError::Qcow2Error {
message: format!("PlistBuddy read failed: {e}"),
})?;
if !output.status.success() {
return Err(TestbedError::Qcow2Error {
message: format!(
"PlistBuddy read {key} failed: {}",
String::from_utf8_lossy(&output.stderr)
),
});
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn plist_set(plist_path: &Path, key: &str, value: &str) -> Result<()> {
let output = std::process::Command::new("/usr/libexec/PlistBuddy")
.args([
"-c",
&format!("Set :{key} {value}"),
plist_path.to_str().unwrap(),
])
.output()
.map_err(|e| TestbedError::Qcow2Error {
message: format!("PlistBuddy set failed: {e}"),
})?;
if !output.status.success() {
return Err(TestbedError::Qcow2Error {
message: format!(
"PlistBuddy set {key} failed: {}",
String::from_utf8_lossy(&output.stderr)
),
});
}
Ok(())
}
pub fn get_vm_name(bundle_path: &Path) -> Result<String> {
let plist = config_plist_path(bundle_path);
plist_get(&plist, NAME_KEY)
}
pub fn set_vm_name(bundle_path: &Path, name: &str) -> Result<()> {
let plist = config_plist_path(bundle_path);
plist_set(&plist, NAME_KEY, "ed_plist_string(name))
}
pub fn get_utm_version(bundle_path: &Path) -> Result<String> {
let plist = config_plist_path(bundle_path);
plist_get(&plist, UTM_VERSION_KEY)
}
pub fn config_plist_path(bundle_path: &Path) -> PathBuf {
bundle_path.join("config.plist")
}
pub fn is_utm_bundle(path: &Path) -> bool {
path.is_dir()
&& path.extension().and_then(|e| e.to_str()) == Some("utm")
&& config_plist_path(path).exists()
}
pub fn set_cpu_cores(bundle_path: &Path, cores: u32) -> Result<()> {
let plist = config_plist_path(bundle_path);
plist_set(&plist, "CPUCount", &cores.to_string())
}
pub fn set_memory_mb(bundle_path: &Path, mb: u32) -> Result<()> {
let plist = config_plist_path(bundle_path);
plist_set(&plist, "MemorySize", &mb.to_string())
}
pub fn quoted_plist_string(s: &str) -> String {
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{escaped}\"")
}
pub fn apply_profile(bundle_path: &Path, profile: &VmProfile) -> Result<()> {
set_vm_name(bundle_path, profile.name)?;
if profile.cpu_cores > 0 {
set_cpu_cores(bundle_path, profile.cpu_cores)?;
}
if profile.memory_mib > 0 {
set_memory_mb(bundle_path, profile.memory_mib)?;
}
Ok(())
}