Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/cmdline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ use clap::Parser;
#[command(version, about, long_about = None)]
pub struct Args {
/// Number of vCPUs for the VM.
#[arg(long, short)]
#[arg(long, short, default_value_t = 2)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is max CPUs worth considering?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what sense? As in defaulting to max CPUs?

Copy link
Member

@ericcurtin ericcurtin Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... I mean for most generic usage all cores is what would be desired I think? Using less cores than available seems like the exception rather than the norm...

pub cpus: u8,

/// Amount of RAM available to VM.
#[arg(long, short)]
#[arg(long, short, default_value_t = 4096)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entry-level M1 Mac Minis only have 8GB of memory. Do we think 50% of that is going to be ok?

Copy link
Member

@ericcurtin ericcurtin Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I wouldn't have it any lower, 4GB is the max it will use right? I mean Chrome on the host is capable of using 8GB but we don't limit it. Plus the M1 Mac Mini is notoriously underspec'd in terms of RAM and like 4 generations old now? M5 is out soon. People can always manually set 2GB, etc. in exceptional cases which in 2025, this probably is an exceptional case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, a VM configured with 4GB doesn't necessarily need to be using all its RAM, virtio-balloon is there to return free memory. In addition to this, macOS is very good (TBH, way more than Linux) at managing its swap memory (having an MacBook M1 Air with 8GB myself, I can attest this is the case), so I'd say this is pretty safe.

pub memory: u32,

/// Bootloader configuration.
Expand Down Expand Up @@ -59,6 +59,9 @@ pub struct Args {
/// Path of log file
#[arg(long = "log-file")]
pub log_file: Option<PathBuf>,

/// Disk image for easy mode.
pub disk_image: Option<String>,
}

/// Parse the input string into a hash map of key value pairs, associating the argument with its
Expand Down
91 changes: 87 additions & 4 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,40 @@ use super::*;

use crate::{
status::{get_shutdown_eventfd, status_listener, RestfulUri},
virtio::KrunContextSet,
virtio::{DiskImageFormat, KrunContextSet},
};

use std::os::fd::{AsRawFd, RawFd};
use std::process::Command;
use std::{convert::TryFrom, ptr, thread};
use std::{
ffi::{c_char, CString},
fs::OpenOptions,
io,
ffi::{c_char, c_int, CString},
fs::{File, OpenOptions},
io::{self, Read},
str::FromStr,
};

use anyhow::{anyhow, Context};
use env_logger::{Builder, Env, Target};
use mac_address::MacAddress;

#[link(name = "krun-efi")]
extern "C" {
fn krun_add_disk2(
ctx_id: u32,
c_block_id: *const c_char,
c_disk_path: *const c_char,
disk_format: u32,
read_only: bool,
) -> i32;
fn krun_add_net_unixstream(
ctx_id: u32,
c_path: *const c_char,
fd: c_int,
c_mac: *const u8,
features: u32,
flags: u32,
) -> i32;
fn krun_create_ctx() -> i32;
fn krun_init_log(target: RawFd, level: u32, style: u32, options: u32) -> i32;
fn krun_set_gpu_options2(ctx_id: u32, virgl_flags: u32, shm_size: u64) -> i32;
Expand Down Expand Up @@ -50,6 +68,23 @@ pub const KRUN_LOG_STYLE_AUTO: u32 = 0;
pub const KRUN_LOG_OPTION_ENV: u32 = 0;
pub const KRUN_LOG_OPTION_NO_ENV: u32 = 1;

const QCOW_MAGIC: [u8; 4] = [0x51, 0x46, 0x49, 0xfb];

fn get_image_format(disk_image: String) -> Result<DiskImageFormat, io::Error> {
let mut file = File::open(disk_image)?;

let mut buffer = [0u8; 4];
file.read_exact(&mut buffer)?;

if buffer == QCOW_MAGIC {
println!("Image detected as Qcow2");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of a general UX question: How do we decide what should be logged and what should be printed to stdout? In other words, why do we want to print this to stdout instead of using info!(), for example?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use println! for logging. This should likely be a debug! rather than println!.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should never print, as it messes up the console. The only reason those are there it's because this is just an early draft.

Ok(DiskImageFormat::Qcow2)
} else {
println!("Image deteced as Raw");
Ok(DiskImageFormat::Raw)
}
}

/// A wrapper of all data used to configure the krun VM.
pub struct KrunContext {
id: u32,
Expand Down Expand Up @@ -141,6 +176,54 @@ impl TryFrom<Args> for KrunContext {
return Err(anyhow!("unable to set krun vCPU/RAM configuration"));
}

if let Some(ref disk_image) = args.disk_image {
let image_format = get_image_format(disk_image.to_string())?;

if unsafe {
krun_add_disk2(
id,
CString::new("root").unwrap().as_ptr(),
CString::new(disk_image.clone()).unwrap().as_ptr(),
image_format as u32,
false,
)
} < 0
{
return Err(anyhow!("error configuring disk image"));
}

match Command::new("gvproxy")
.arg("-listen-qemu")
.arg("unix:///tmp/gvproxy-krun.sock")
.spawn()
{
Ok(child) => {
println!("Process spawned successfully with PID: {}", child.id());
unsafe {
if krun_add_net_unixstream(
id,
CString::new("/tmp/gvproxy-krun.sock").unwrap().as_ptr(),
-1,
MacAddress::from_str("56:c8:d4:db:e1:47")
.unwrap()
.bytes()
.as_ptr(),
0,
0,
) < 0
{
return Err(anyhow!(format!(
"virtio-net unable to configure default interface",
)));
}
}
}
Err(e) => {
eprintln!("Failed to spawn process: {}", e);
}
}
}

// Configure each virtio device to include in the VM.
for device in &args.devices {
unsafe { device.krun_ctx_set(id)? }
Expand Down
Loading