use base64::engine::general_purpose::STANDARD as B64;
use base64::Engine;
use crate::{Config, WorkloadStrategy};
pub(crate) fn scratch_device_name(config: &Config) -> String {
let index = config.rootfs_block.is_some() as usize + config.disks.len();
let letter = (b'a' + index as u8) as char;
format!("vd{letter}")
}
pub(crate) fn build(
config: &Config,
effective_vsock_port: Option<u32>,
scratch_dev: Option<&str>,
) -> String {
let mut s = config.cmdline.clone();
if let Some(cmd) = &config.exec_cmd {
let b64 = B64.encode(cmd.as_bytes());
s.push_str(" vmette.exec=");
s.push_str(&b64);
}
if let Some(rb) = &config.rootfs_block {
s.push_str(" vmette.rootfs_block=");
s.push_str(rb.fstype.as_str());
} else if let Some(rs) = &config.rootfs_share {
s.push_str(" vmette.rootfs=1");
if rs.read_only {
s.push_str(" vmette.rootfs_ro=1");
}
}
if let Some(dev) = scratch_dev {
s.push_str(" vmette.scratch_dev=");
s.push_str(dev);
}
for sh in &config.shares {
s.push_str(" vmette.share=");
s.push_str(&sh.tag);
}
if config.switch_root {
s.push_str(" vmette.switch_root=1");
}
if config.net {
s.push_str(" vmette.net=1");
}
if let Some(port) = effective_vsock_port {
s.push_str(&format!(" vmette.vsock_port={}", port));
}
if config.build_snapshot.is_some() {
s.push_str(" vmette.snapshot_mode=server");
s.push_str(&format!(
" vmette.guest_vsock_port={}",
config.guest_vsock_port
));
}
if config.workload == WorkloadStrategy::Agent {
let (w, h) = config.display_size;
s.push_str(" vmette.desktop=1");
s.push_str(&format!(" vmette.display={}x{}", w, h));
}
if let Some(env) = crate::render_env_exports(&config.env) {
s.push_str(" vmette.env=");
s.push_str(&B64.encode(env.as_bytes()));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn base() -> Config {
Config::new("/k", "/i")
}
#[test]
fn empty_cmdline_keeps_base() {
let s = build(&base(), None, None);
assert_eq!(s, "console=hvc0 quiet");
}
#[test]
fn exec_cmd_is_base64_encoded() {
let mut c = base();
c.exec_cmd = Some("echo hi".into());
let s = build(&c, None, None);
assert!(s.contains("vmette.exec="));
assert!(s.contains("ZWNobyBoaQ=="));
}
#[test]
fn rootfs_ro_emits_both_keys() {
let mut c = base();
c.rootfs_share = Some(crate::RootfsShare {
path: PathBuf::from("/r"),
read_only: true,
});
let s = build(&c, None, None);
assert!(s.contains("vmette.rootfs=1"));
assert!(s.contains("vmette.rootfs_ro=1"));
}
#[test]
fn rootfs_block_emits_fstype_and_suppresses_share() {
let mut c = base();
c.rootfs_block = Some(crate::RootfsBlock {
path: PathBuf::from("/img.sqfs"),
fstype: crate::BlockFs::Squashfs,
});
let s = build(&c, None, None);
assert!(s.contains("vmette.rootfs_block=squashfs"));
assert!(!s.contains("vmette.rootfs=1"));
}
#[test]
fn desktop_tokens_only_emitted_for_agent() {
let s = build(&base(), None, None);
assert!(!s.contains("vmette.desktop"));
let mut c = base();
c.workload = crate::WorkloadStrategy::Agent;
c.display_size = (1024, 768);
let s = build(&c, None, None);
assert!(s.contains("vmette.desktop=1"));
assert!(s.contains("vmette.display=1024x768"));
}
#[test]
fn env_emitted_only_when_set_and_base64_encoded() {
let s = build(&base(), None, None);
assert!(!s.contains("vmette.env="));
let mut c = base();
c.env = vec![("FOO".into(), "bar".into())];
let s = build(&c, None, None);
let want = base64::engine::general_purpose::STANDARD.encode("export FOO='bar'\n");
assert!(s.contains(&format!("vmette.env={want}")));
}
#[test]
fn scratch_dev_only_emitted_when_requested() {
let mut c = base();
c.rootfs_share = Some(crate::RootfsShare {
path: PathBuf::from("/r"),
read_only: false,
});
assert!(!build(&c, None, None).contains("vmette.scratch_dev"));
assert_eq!(scratch_device_name(&c), "vda");
assert!(build(&c, None, Some("vda")).contains("vmette.scratch_dev=vda"));
}
#[test]
fn scratch_dev_index_follows_block_rootfs_and_disks() {
let mut c = base();
c.rootfs_block = Some(crate::RootfsBlock {
path: PathBuf::from("/img.sqfs"),
fstype: crate::BlockFs::Squashfs,
});
assert_eq!(scratch_device_name(&c), "vdb");
c.disks = vec![PathBuf::from("/d1"), PathBuf::from("/d2")];
assert_eq!(scratch_device_name(&c), "vdd");
let mut d = base();
d.disks = vec![PathBuf::from("/d1")];
assert_eq!(scratch_device_name(&d), "vdb");
}
#[test]
fn vsock_port_only_emitted_when_some() {
let s = build(&base(), Some(55555), None);
assert!(s.contains("vmette.vsock_port=55555"));
let s = build(&base(), None, None);
assert!(!s.contains("vmette.vsock_port"));
}
}