Initial commit

This commit is contained in:
2024-03-04 17:55:39 +02:00
commit d20b34f953
7 changed files with 537 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "qemu"
version = "0.1.0"
+6
View File
@@ -0,0 +1,6 @@
[package]
name = "qemu"
version = "0.1.0"
edition = "2021"
[dependencies]
+153
View File
@@ -0,0 +1,153 @@
use std::{path::PathBuf, process::Command};
use crate::{Architecture, IntoArgs};
#[derive(Debug)]
pub enum Machine {
Virt { virtualize: bool },
}
#[derive(Debug)]
pub enum Cpu {
CortexA57,
}
#[derive(Debug)]
pub enum Image {
Kernel {
kernel: PathBuf,
initrd: Option<PathBuf>,
},
}
#[derive(Debug)]
pub struct QemuAArch64;
impl IntoArgs for Machine {
fn add_args(&self, command: &mut Command) {
command.arg("-M");
match self {
&Self::Virt { virtualize } => {
let mut arg = "virt".to_owned();
if virtualize {
arg.push_str(",virtualization=on");
}
command.arg(arg);
}
}
}
}
impl IntoArgs for Cpu {
fn add_args(&self, command: &mut Command) {
command.arg("-cpu");
match self {
Self::CortexA57 => {
command.arg("cortex-a57");
}
}
}
}
impl IntoArgs for Image {
fn add_args(&self, command: &mut Command) {
match self {
Self::Kernel { kernel, initrd } => {
command.arg("-kernel").arg(kernel);
if let Some(initrd) = initrd {
command.arg("-initrd").arg(initrd);
}
}
}
}
}
impl IntoArgs for QemuAArch64 {
fn add_args(&self, _command: &mut Command) {}
}
impl Architecture for QemuAArch64 {
type MachineType = Machine;
type CpuType = Cpu;
type ImageType = Image;
const DEFAULT_COMMAND: &'static str = "qemu-system-aarch64";
}
#[cfg(test)]
mod tests {
use crate::{aarch64::Machine, device::QemuSerialTarget, Qemu};
use super::{Cpu, Image};
#[test]
fn boot_options() {
let mut qemu = Qemu::new_aarch64();
qemu.with_boot_image(Image::Kernel {
kernel: "my-kernel.bin".into(),
initrd: Some("my-initrd.img".into()),
});
let command = qemu.into_command();
assert_eq!(
command.get_args().collect::<Vec<_>>(),
vec!["-kernel", "my-kernel.bin", "-initrd", "my-initrd.img"]
);
let mut qemu = Qemu::new_aarch64();
qemu.with_boot_image(Image::Kernel {
kernel: "my-kernel.bin".into(),
initrd: None,
});
let command = qemu.into_command();
assert_eq!(
command.get_args().collect::<Vec<_>>(),
vec!["-kernel", "my-kernel.bin"]
);
}
#[test]
fn machine_options() {
let mut qemu = Qemu::new_aarch64();
qemu.with_machine(Machine::Virt { virtualize: true });
let command = qemu.into_command();
assert_eq!(
command.get_args().collect::<Vec<_>>(),
vec!["-M", "virt,virtualization=on"]
);
}
#[test]
fn full_commands() {
let mut qemu = Qemu::new_aarch64();
qemu.override_qemu("my-qemu");
qemu.with_boot_image(Image::Kernel {
kernel: "my-kernel.bin".into(),
initrd: Some("my-initrd.img".into()),
});
qemu.with_machine(Machine::Virt { virtualize: false });
qemu.with_cpu(Cpu::CortexA57);
qemu.with_serial(QemuSerialTarget::MonStdio);
let command = qemu.into_command();
assert_eq!(
command.get_args().collect::<Vec<_>>(),
vec![
"-serial",
"mon:stdio",
"-M",
"virt",
"-cpu",
"cortex-a57",
"-kernel",
"my-kernel.bin",
"-initrd",
"my-initrd.img"
]
);
}
}
+80
View File
@@ -0,0 +1,80 @@
use std::process::Command;
use crate::IntoArgs;
#[derive(Debug)]
pub enum QemuNic {
VirtioPci { mac: Option<String> },
}
#[derive(Debug)]
pub enum QemuDevice {
NetworkTap {
nic: QemuNic,
script: Option<String>,
ifname: Option<String>,
},
}
#[derive(Debug)]
pub enum QemuSerialTarget {
MonStdio,
Stdio,
}
impl IntoArgs for QemuNic {
fn add_args(&self, command: &mut Command) {
match self {
Self::VirtioPci { mac } => {
command.arg("-device");
let mut val = "virtio-net-pci,netdev=net0".to_owned();
if let Some(mac) = mac {
val.push_str(",mac=");
val.push_str(mac);
}
command.arg(val);
}
}
}
}
// TODO separate handling for devices
impl IntoArgs for QemuDevice {
fn add_args(&self, command: &mut Command) {
match self {
Self::NetworkTap {
nic,
script,
ifname,
} => {
command.arg("-netdev");
let mut val = "tap,id=net0".to_owned();
if let Some(script) = script {
val.push_str(",script=");
val.push_str(script);
}
if let Some(ifname) = ifname {
val.push_str(",ifname=");
val.push_str(ifname);
}
command.arg(val);
nic.add_args(command);
}
}
}
}
impl IntoArgs for QemuSerialTarget {
fn add_args(&self, command: &mut Command) {
command.arg("-serial");
match self {
Self::MonStdio => {
command.arg("mon:stdio");
}
Self::Stdio => {
command.arg("stdio");
}
}
}
}
+206
View File
@@ -0,0 +1,206 @@
use std::{fmt::Debug, path::PathBuf, process::Command};
use device::{QemuDevice, QemuSerialTarget};
pub mod aarch64;
pub mod x86_64;
pub mod device;
pub trait Architecture: IntoArgs {
type MachineType: IntoArgs;
type CpuType: IntoArgs;
type ImageType: IntoArgs;
const DEFAULT_COMMAND: &'static str;
}
pub trait IntoArgs {
fn add_args(&self, command: &mut Command);
}
impl<T: IntoArgs> IntoArgs for Option<T> {
fn add_args(&self, command: &mut Command) {
if let Some(val) = self {
val.add_args(command);
}
}
}
#[derive(Debug, Default)]
struct QemuCommon {
devices: Vec<QemuDevice>,
serial: Option<QemuSerialTarget>,
}
#[derive(Debug, Default)]
pub struct Qemu<A: Architecture> {
binary_override: Option<PathBuf>,
common: QemuCommon,
cpu: Option<A::CpuType>,
machine: Option<A::MachineType>,
boot_image: Option<A::ImageType>,
no_display: bool,
memory_megabytes: Option<usize>,
smp: Option<usize>,
arch: A,
}
impl Qemu<aarch64::QemuAArch64> {
pub fn new_aarch64() -> Self {
Qemu::new(aarch64::QemuAArch64)
}
}
impl Qemu<x86_64::QemuX86_64> {
pub fn new_x86_64() -> Self {
Qemu::new(x86_64::QemuX86_64::default())
}
pub fn with_bios_image(&mut self, image: PathBuf) -> &mut Self {
self.arch.bios_image = Some(image);
self
}
pub fn with_boot_slot(&mut self, slot: char) -> &mut Self {
self.arch.boot_slot = Some(slot);
self
}
}
impl<A: Architecture> Qemu<A> {
pub fn new(arch: A) -> Self {
Self {
binary_override: None,
common: QemuCommon::default(),
memory_megabytes: None,
no_display: false,
cpu: None,
machine: None,
boot_image: None,
smp: None,
arch,
}
}
pub fn with_device(&mut self, device: QemuDevice) -> &mut Self {
self.common.devices.push(device);
self
}
pub fn with_memory_megabytes(&mut self, value: usize) -> &mut Self {
self.memory_megabytes = Some(value);
self
}
pub fn with_serial(&mut self, serial: QemuSerialTarget) -> &mut Self {
self.common.serial = Some(serial);
self
}
pub fn with_boot_image(&mut self, image: A::ImageType) -> &mut Self {
self.boot_image = Some(image);
self
}
pub fn with_cpu(&mut self, cpu: A::CpuType) -> &mut Self {
self.cpu = Some(cpu);
self
}
pub fn with_machine(&mut self, machine: A::MachineType) -> &mut Self {
self.machine = Some(machine);
self
}
pub fn disable_display(&mut self) -> &mut Self {
self.no_display = true;
self
}
pub fn override_qemu<S: Into<PathBuf>>(&mut self, qemu: S) -> &mut Self {
self.binary_override = Some(qemu.into());
self
}
pub fn into_command(self) -> Command {
let qemu = self
.binary_override
.clone()
.unwrap_or_else(|| A::DEFAULT_COMMAND.into());
let mut command = Command::new(qemu);
self.add_args(&mut command);
command
}
}
impl IntoArgs for QemuCommon {
fn add_args(&self, command: &mut Command) {
self.serial.add_args(command);
}
}
impl<A: Architecture> IntoArgs for Qemu<A> {
fn add_args(&self, command: &mut Command) {
self.common.add_args(command);
for device in &self.common.devices {
device.add_args(command);
}
if let Some(mem) = self.memory_megabytes {
command.args(["-m", &format!("{}m", mem)]);
}
self.machine.add_args(command);
self.cpu.add_args(command);
self.boot_image.add_args(command);
if self.no_display {
command.args(["-display", "none"]);
}
if let Some(smp) = self.smp {
command.args(["-smp", &format!("{}", smp)]);
}
self.arch.add_args(command);
}
}
#[cfg(test)]
mod tests {
use crate::{device::QemuSerialTarget, Qemu};
#[test]
fn empty() {
let builder = Qemu::new_aarch64();
let command = builder.into_command();
assert_eq!(command.get_program(), "qemu-system-aarch64");
assert_eq!(command.get_args().len(), 0);
}
#[test]
fn qemu_override() {
let mut qemu = Qemu::new_aarch64();
qemu.override_qemu("test-qemu");
let command = qemu.into_command();
assert_eq!(command.get_program(), "test-qemu");
}
#[test]
fn common_options() {
let mut qemu = Qemu::new_aarch64();
qemu.with_serial(QemuSerialTarget::MonStdio);
let command = qemu.into_command();
assert_eq!(
command.get_args().collect::<Vec<_>>(),
vec!["-serial", "mon:stdio"]
);
}
}
+84
View File
@@ -0,0 +1,84 @@
use std::{path::PathBuf, process::Command};
use crate::{Architecture, IntoArgs};
#[derive(Debug)]
pub enum Machine {
Q35,
}
#[derive(Debug)]
pub enum Cpu {
Host { enable_kvm: bool },
}
#[derive(Debug)]
pub enum Image {
Drive(PathBuf),
}
#[derive(Debug, Default)]
pub struct QemuX86_64 {
pub bios_image: Option<PathBuf>,
pub boot_slot: Option<char>,
}
impl IntoArgs for Machine {
fn add_args(&self, command: &mut Command) {
match self {
Self::Q35 => {
command.args(["-M", "q35"]);
}
}
}
}
impl IntoArgs for Cpu {
fn add_args(&self, command: &mut Command) {
match self {
&Self::Host { enable_kvm } => {
command.args(["-cpu", "host"]);
if enable_kvm {
command.arg("-enable-kvm");
}
}
}
}
}
impl IntoArgs for Image {
fn add_args(&self, command: &mut Command) {
match self {
Self::Drive(path) => {
command.args(["-drive", &format!("format=raw,file={}", path.display())]);
}
}
}
}
impl IntoArgs for QemuX86_64 {
fn add_args(&self, command: &mut Command) {
if let Some(slot) = self.boot_slot {
command.arg("-boot");
command.arg(format!("{}", slot));
}
if let Some(bios_image) = self.bios_image.as_ref() {
command.args([
"-drive",
&format!(
"format=raw,if=pflash,readonly=on,file={}",
bios_image.display()
),
]);
}
}
}
impl Architecture for QemuX86_64 {
type MachineType = Machine;
type CpuType = Cpu;
type ImageType = Image;
const DEFAULT_COMMAND: &'static str = "qemu-system-x86_64";
}