use std::ffi::{c_char, CStr};
use std::path::PathBuf;
use std::ptr;
use crate::{Config, Error, RootfsShare, RunOutput, ShareMount, VsockPort};
#[repr(C)]
pub struct vmette_config_t {
_private: [u8; 0],
}
#[repr(C)]
pub struct vmette_run_output_t {
_private: [u8; 0],
}
#[repr(i32)]
pub enum VmetteStatus {
Ok = 0,
InvalidConfig = 1,
StartFailed = 2,
RestoreFailed = 3,
SaveFailed = 4,
SnapshotUnsupported = 5,
Timeout = 6,
Vsock = 7,
Io = 8,
NullArg = 9,
InvalidUtf8 = 10,
}
impl From<&Error> for VmetteStatus {
fn from(e: &Error) -> Self {
match e {
Error::InvalidConfig(_) => Self::InvalidConfig,
Error::StartFailed(_) => Self::StartFailed,
Error::RestoreFailed(_) => Self::RestoreFailed,
Error::SaveFailed(_) => Self::SaveFailed,
Error::SnapshotUnsupported => Self::SnapshotUnsupported,
Error::Timeout(_) => Self::Timeout,
Error::Vsock(_) => Self::Vsock,
Error::Io(_) => Self::Io,
}
}
}
unsafe fn cstr_to_string(p: *const c_char) -> Option<String> {
if p.is_null() {
return None;
}
CStr::from_ptr(p).to_str().ok().map(String::from)
}
unsafe fn cstr_to_pathbuf(p: *const c_char) -> Option<PathBuf> {
cstr_to_string(p).map(PathBuf::from)
}
unsafe fn cfg_mut<'a>(p: *mut vmette_config_t) -> Option<&'a mut Config> {
if p.is_null() {
return None;
}
Some(&mut *(p as *mut Config))
}
unsafe fn cfg_ref<'a>(p: *const vmette_config_t) -> Option<&'a Config> {
if p.is_null() {
return None;
}
Some(&*(p as *const Config))
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_new(
kernel: *const c_char,
initramfs: *const c_char,
) -> *mut vmette_config_t {
let Some(kernel) = cstr_to_pathbuf(kernel) else {
return ptr::null_mut();
};
let Some(initramfs) = cstr_to_pathbuf(initramfs) else {
return ptr::null_mut();
};
let cfg = Box::new(Config::new(kernel, initramfs));
Box::into_raw(cfg) as *mut vmette_config_t
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_free(cfg: *mut vmette_config_t) {
if cfg.is_null() {
return;
}
drop(Box::from_raw(cfg as *mut Config));
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_cmdline(
cfg: *mut vmette_config_t,
cmdline: *const c_char,
) {
let Some(c) = cfg_mut(cfg) else { return };
if let Some(s) = cstr_to_string(cmdline) {
c.cmdline = s;
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_rootfs_share(
cfg: *mut vmette_config_t,
path: *const c_char,
read_only: bool,
) {
let Some(c) = cfg_mut(cfg) else { return };
if let Some(p) = cstr_to_pathbuf(path) {
c.rootfs_share = Some(RootfsShare { path: p, read_only });
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_add_share(
cfg: *mut vmette_config_t,
tag: *const c_char,
path: *const c_char,
) {
let Some(c) = cfg_mut(cfg) else { return };
let Some(tag) = cstr_to_string(tag) else {
return;
};
let Some(path) = cstr_to_pathbuf(path) else {
return;
};
c.shares.push(ShareMount { tag, path });
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_add_disk(cfg: *mut vmette_config_t, path: *const c_char) {
let Some(c) = cfg_mut(cfg) else { return };
if let Some(p) = cstr_to_pathbuf(path) {
c.disks.push(p);
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_exec(cfg: *mut vmette_config_t, cmd: *const c_char) {
let Some(c) = cfg_mut(cfg) else { return };
c.exec_cmd = cstr_to_string(cmd);
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_net(cfg: *mut vmette_config_t, enable: bool) {
if let Some(c) = cfg_mut(cfg) {
c.net = enable;
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_switch_root(cfg: *mut vmette_config_t, enable: bool) {
if let Some(c) = cfg_mut(cfg) {
c.switch_root = enable;
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_vsock_port(cfg: *mut vmette_config_t, port: i32) {
let Some(c) = cfg_mut(cfg) else { return };
c.vsock_port = match port {
n if n < 0 => VsockPort::Disabled,
0 => VsockPort::Auto,
n => VsockPort::Fixed(n as u32),
};
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_guest_vsock_port(cfg: *mut vmette_config_t, port: u32) {
if let Some(c) = cfg_mut(cfg) {
c.guest_vsock_port = port;
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_timeout(cfg: *mut vmette_config_t, seconds: u32) {
if let Some(c) = cfg_mut(cfg) {
c.timeout_seconds = if seconds == 0 { None } else { Some(seconds) };
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_vcpus(cfg: *mut vmette_config_t, n: u8) {
if let Some(c) = cfg_mut(cfg) {
c.vcpus = n;
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_mem_mib(cfg: *mut vmette_config_t, n: u64) {
if let Some(c) = cfg_mut(cfg) {
c.mem_mib = n;
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_build_snapshot(
cfg: *mut vmette_config_t,
path: *const c_char,
) {
let Some(c) = cfg_mut(cfg) else { return };
c.build_snapshot = cstr_to_pathbuf(path);
}
#[no_mangle]
pub unsafe extern "C" fn vmette_config_set_resume_snapshot(
cfg: *mut vmette_config_t,
path: *const c_char,
) {
let Some(c) = cfg_mut(cfg) else { return };
c.resume_snapshot = cstr_to_pathbuf(path);
}
#[no_mangle]
pub unsafe extern "C" fn vmette_run(
cfg: *const vmette_config_t,
out: *mut *mut vmette_run_output_t,
) -> VmetteStatus {
let Some(c) = cfg_ref(cfg) else {
return VmetteStatus::NullArg;
};
if out.is_null() {
return VmetteStatus::NullArg;
}
match crate::run(c) {
Ok(r) => {
let boxed = Box::new(r);
*out = Box::into_raw(boxed) as *mut vmette_run_output_t;
VmetteStatus::Ok
}
Err(e) => VmetteStatus::from(&e),
}
}
#[no_mangle]
pub unsafe extern "C" fn vmette_run_output_exit_code(out: *const vmette_run_output_t) -> i32 {
if out.is_null() {
return 0;
}
let r = &*(out as *const RunOutput);
r.exit_code
}
#[no_mangle]
pub unsafe extern "C" fn vmette_run_output_free(out: *mut vmette_run_output_t) {
if out.is_null() {
return;
}
drop(Box::from_raw(out as *mut RunOutput));
}
#[no_mangle]
pub extern "C" fn vmette_version() -> *const c_char {
static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
VERSION.as_ptr() as *const c_char
}