use anyhow::{Context, Result};
use std::path::Path;
pub(crate) struct MemoryBudget {
pub uncompressed_initramfs_bytes: u64,
pub compressed_initrd_bytes: u64,
pub kernel_init_size: u64,
}
pub(crate) fn read_kernel_init_size(kernel_path: &Path) -> Result<u64> {
use std::io::{Read, Seek, SeekFrom};
let mut f = std::fs::File::open(kernel_path)
.with_context(|| format!("open kernel for init_size: {}", kernel_path.display()))?;
#[cfg(target_arch = "x86_64")]
{
f.seek(SeekFrom::Start(0x260))
.context("seek to init_size in bzImage")?;
let mut buf = [0u8; 4];
f.read_exact(&mut buf)
.context("read init_size from bzImage")?;
Ok(u32::from_le_bytes(buf) as u64)
}
#[cfg(target_arch = "aarch64")]
{
let mut magic = [0u8; 2];
f.read_exact(&mut magic).context("read kernel magic")?;
if magic == [0x1f, 0x8b] {
f.seek(SeekFrom::Start(0))
.context("seek vmlinuz to start")?;
let mut decoder = flate2::read::GzDecoder::new(&mut f);
let mut header = [0u8; 24];
decoder
.read_exact(&mut header)
.context("decompress arm64 vmlinuz header for image_size")?;
return Ok(u64::from_le_bytes(header[16..24].try_into().unwrap()));
}
f.seek(SeekFrom::Start(16))
.context("seek to image_size in arm64 Image")?;
let mut buf = [0u8; 8];
f.read_exact(&mut buf)
.context("read image_size from arm64 Image")?;
Ok(u64::from_le_bytes(buf))
}
}
const WORKLOAD_MB: u64 = 256;
pub(crate) fn initramfs_min_memory_mb(budget: &MemoryBudget) -> u32 {
let ceil_mb = |bytes: u64| -> u64 { (bytes + (1 << 20) - 1) >> 20 };
let init_size_mb = ceil_mb(budget.kernel_init_size);
let compressed_mb = ceil_mb(budget.compressed_initrd_bytes);
let uncompressed_mb = ceil_mb(budget.uncompressed_initramfs_bytes);
let uncompressed_scaled = (uncompressed_mb * 10).div_ceil(9);
let content_mb = uncompressed_scaled + init_size_mb + compressed_mb;
let boot_mb = (content_mb * 64).div_ceil(63);
(boot_mb + WORKLOAD_MB) as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn workload_mb_is_256() {
assert_eq!(WORKLOAD_MB, 256);
}
#[test]
fn initramfs_min_memory_mb_zeros_returns_workload_budget() {
let budget = MemoryBudget {
uncompressed_initramfs_bytes: 0,
compressed_initrd_bytes: 0,
kernel_init_size: 0,
};
assert_eq!(initramfs_min_memory_mb(&budget), WORKLOAD_MB as u32);
}
#[test]
fn initramfs_min_memory_mb_known_input() {
let budget = MemoryBudget {
uncompressed_initramfs_bytes: 10 * (1 << 20),
compressed_initrd_bytes: 2 * (1 << 20),
kernel_init_size: 5 * (1 << 20),
};
assert_eq!(initramfs_min_memory_mb(&budget), 276);
}
#[test]
fn initramfs_min_memory_mb_subbyte_uncompressed_rounds_up() {
let budget = MemoryBudget {
uncompressed_initramfs_bytes: 1,
compressed_initrd_bytes: 0,
kernel_init_size: 0,
};
assert_eq!(initramfs_min_memory_mb(&budget), 259);
}
#[test]
fn initramfs_min_memory_mb_larger_input() {
let budget = MemoryBudget {
uncompressed_initramfs_bytes: 200 * (1 << 20),
compressed_initrd_bytes: 50 * (1 << 20),
kernel_init_size: 30 * (1 << 20),
};
assert_eq!(initramfs_min_memory_mb(&budget), 564);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn read_kernel_init_size_x86_64_reads_offset_0x260() {
use std::io::Write;
let mut f = tempfile::NamedTempFile::new().expect("tempfile");
let pad = vec![0u8; 0x260];
f.write_all(&pad).expect("write pad");
let init_size: u32 = 0x1234_5678;
f.write_all(&init_size.to_le_bytes())
.expect("write init_size");
f.flush().expect("flush");
let got = read_kernel_init_size(f.path()).expect("read init_size");
assert_eq!(got, init_size as u64);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn read_kernel_init_size_x86_64_short_file_errors() {
use std::io::Write;
let mut f = tempfile::NamedTempFile::new().expect("tempfile");
let truncated = vec![0u8; 0x100];
f.write_all(&truncated).expect("write truncated");
f.flush().expect("flush");
let result = read_kernel_init_size(f.path());
assert!(result.is_err(), "truncated file must fail; got: {result:?}",);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn read_kernel_init_size_aarch64_reads_offset_16() {
use std::io::Write;
let mut f = tempfile::NamedTempFile::new().expect("tempfile");
let prefix = [0u8; 16];
f.write_all(&prefix).expect("write prefix");
let image_size: u64 = 0x1234_5678_9abc_def0;
f.write_all(&image_size.to_le_bytes())
.expect("write image_size");
f.flush().expect("flush");
let got = read_kernel_init_size(f.path()).expect("read image_size");
assert_eq!(got, image_size);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn read_kernel_init_size_aarch64_short_file_errors() {
use std::io::Write;
let mut f = tempfile::NamedTempFile::new().expect("tempfile");
let truncated = vec![0u8; 8];
f.write_all(&truncated).expect("write truncated");
f.flush().expect("flush");
let result = read_kernel_init_size(f.path());
assert!(result.is_err(), "truncated file must fail; got: {result:?}",);
}
}