use crate::util::is_printable_ascii;
use crate::vmm::Entrypoint;
pub(crate) struct GuestEntrypointBuilder {
executable: String,
args: Vec<String>,
env: Vec<(String, String)>,
total_size: usize,
limit: usize,
}
impl GuestEntrypointBuilder {
#[cfg(target_arch = "aarch64")]
const KERNEL_CMDLINE_MAX_SIZE: usize = 2048;
#[cfg(target_arch = "x86_64")]
const KERNEL_CMDLINE_MAX_SIZE: usize = 65536;
const ENV_VAR_OVERHEAD: usize = 4;
const LIBKRUN_CMDLINE_FIXED_OVERHEAD: usize = 300;
pub fn new(executable: String) -> Self {
let limit = Self::calculate_limit(&executable);
tracing::debug!(
arch_limit = Self::KERNEL_CMDLINE_MAX_SIZE,
env_space_limit = limit,
"Calculated env space limit for kernel cmdline"
);
Self {
executable,
args: Vec::new(),
env: Vec::new(),
total_size: 0,
limit,
}
}
pub fn with_env(&mut self, key: &str, value: &str) -> bool {
if let Some(pos) = self.env.iter().position(|(k, _)| k == key) {
let (old_key, old_value) = &self.env[pos];
let old_size = old_key.len() + old_value.len() + Self::ENV_VAR_OVERHEAD;
self.total_size = self.total_size.saturating_sub(old_size);
self.env.remove(pos);
tracing::trace!(env_key = %key, "Overriding existing env var");
}
if !is_printable_ascii(key) || !is_printable_ascii(value) {
tracing::warn!(
env_key = %key,
env_value = %Self::redact_for_log(value),
"Skipping env var: contains non-ASCII characters"
);
return false;
}
let var_size = key.len() + value.len() + Self::ENV_VAR_OVERHEAD;
if self.total_size + var_size > self.limit {
tracing::warn!(
env_key = %key,
env_value = %Self::redact_for_log(value),
total_size = self.total_size,
var_size,
limit = self.limit,
"Skipping env var: kernel cmdline size limit reached"
);
return false;
}
self.total_size += var_size;
self.env.push((key.to_string(), value.to_string()));
tracing::trace!(env_key = %key, var_size, "Added env var");
true
}
pub fn with_arg(&mut self, arg: &str) {
let arg_size = arg.len() + 1; self.limit = self.limit.saturating_sub(arg_size);
self.args.push(arg.to_string());
tracing::trace!(arg, arg_size, new_limit = self.limit, "Added arg");
}
pub fn build(self) -> Entrypoint {
tracing::debug!(
env_count = self.env.len(),
total_size = self.total_size,
limit = self.limit,
"Final env vars for guest entrypoint"
);
Entrypoint {
executable: self.executable,
args: self.args,
env: self.env,
}
}
fn calculate_limit(executable: &str) -> usize {
let exec_size = executable.len() + 1; Self::KERNEL_CMDLINE_MAX_SIZE
.saturating_sub(exec_size)
.saturating_sub(Self::LIBKRUN_CMDLINE_FIXED_OVERHEAD)
}
fn redact_for_log(value: &str) -> String {
if value.len() <= 8 {
"***".to_string()
} else {
format!(
"{}...{} ({} chars)",
&value[..4],
&value[value.len() - 4..],
value.len()
)
}
}
}