#![expect(unsafe_code)]
#![expect(missing_docs)]
use core::slice;
use disk_backend::Disk;
use disk_vhd1::Vhd1Disk;
use futures::executor::block_on;
use std::ffi::CStr;
use std::ffi::c_char;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use vmgs::EncryptionAlgorithm;
use vmgs::Vmgs;
use vmgs_format::FileId;
use vmgs_format::VMGS_BYTES_PER_BLOCK;
use vmgs_format::VMGS_DEFAULT_CAPACITY;
#[repr(u32)]
pub enum VmgsError {
Ok = 0,
NullParam = 1,
CantOpenFile = 2,
CantReadFile = 3,
FileDisk = 4,
InvalidBufSize = 5,
InvalidFileID = 6,
InvalidFileSize = 7,
InvalidString = 8,
InvalidVmgs = 9,
FileInfoAllocated = 10,
DecryptionFailed = 11,
EncryptionFailed = 12,
WriteFailed = 13,
FileExists = 14,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn read_vmgs(
file_path: *const c_char,
file_id: FileId,
encryption_key: *const c_char,
use_encryption: bool,
in_buf: *mut u8,
in_len: u64,
) -> VmgsError {
let file_path = unsafe {
if file_path.is_null() {
return VmgsError::NullParam;
}
match CStr::from_ptr(file_path).to_str() {
Ok(res) => res,
Err(_res) => return VmgsError::InvalidString,
}
};
let buf = unsafe {
if in_buf.is_null() {
return VmgsError::NullParam;
}
slice::from_raw_parts_mut(in_buf, in_len as usize)
};
let key = match use_encryption {
true => unsafe {
if encryption_key.is_null() {
return VmgsError::NullParam;
}
match CStr::from_ptr(encryption_key).to_str() {
Ok(res) => Some(res.as_bytes()),
Err(_res) => return VmgsError::InvalidString,
}
},
false => None,
};
let data = match block_on(do_read(file_path, file_id, key)) {
Ok(value) => value,
Err(value) => return value,
};
if data.len() != in_len as usize {
return VmgsError::InvalidBufSize;
}
buf.copy_from_slice(&data);
VmgsError::Ok
}
fn open_disk(file_path: &str, read_only: bool) -> Result<Disk, VmgsError> {
let file = File::options()
.read(true)
.write(!read_only)
.open(file_path)
.map_err(|_| VmgsError::FileDisk)?;
let disk = Vhd1Disk::open_fixed(file, read_only).map_err(|_| VmgsError::FileDisk)?;
Disk::new(disk).map_err(|_| VmgsError::FileDisk)
}
async fn do_read(
file_path: &str,
file_id: FileId,
key: Option<&[u8]>,
) -> Result<Vec<u8>, VmgsError> {
let mut vmgs = Vmgs::open(open_disk(file_path, true)?)
.await
.map_err(|_| VmgsError::InvalidVmgs)?;
let info = vmgs
.get_file_info(file_id)
.map_err(|_| VmgsError::FileInfoAllocated)?;
let data_size = info.valid_bytes;
if let Some(encryption_key) = key {
let _key_index = vmgs
.unlock_with_encryption_key(encryption_key)
.await
.map_err(|_| VmgsError::DecryptionFailed)?;
}
let data = vmgs
.read_file(file_id)
.await
.map_err(|_| VmgsError::CantReadFile)?;
if data.len() != data_size as usize {
return Err(VmgsError::CantReadFile);
}
Ok(data)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn write_vmgs(
file_path: *const c_char,
data_path: *const c_char,
file_id: FileId,
encryption_key: *const c_char,
use_encryption: bool,
) -> VmgsError {
let (file_path, data_path) = unsafe {
if file_path.is_null() || data_path.is_null() {
return VmgsError::NullParam;
}
let file = CStr::from_ptr(file_path).to_str();
let data = CStr::from_ptr(data_path).to_str();
match (file, data) {
(Ok(f), Ok(d)) => (f, d),
_ => return VmgsError::InvalidString,
}
};
let key = match use_encryption {
true => {
if encryption_key.is_null() {
return VmgsError::NullParam;
}
match unsafe { CStr::from_ptr(encryption_key) }.to_str() {
Ok(res) => Some(res.as_bytes()),
Err(_res) => return VmgsError::InvalidString,
}
}
false => None,
};
match block_on(do_write(file_path, data_path, key, file_id)) {
Ok(_) => VmgsError::Ok,
Err(ret) => ret,
}
}
async fn do_write(
file_path: &str,
data_path: &str,
key: Option<&[u8]>,
file_id: FileId,
) -> Result<(), VmgsError> {
let mut buf = Vec::new();
let mut file = File::open(data_path).map_err(|_| VmgsError::CantOpenFile)?;
#[expect(clippy::verbose_file_reads)]
file.read_to_end(&mut buf)
.map_err(|_| VmgsError::CantReadFile)?;
let mut vmgs = Vmgs::open(open_disk(file_path, false)?)
.await
.map_err(|_| VmgsError::InvalidVmgs)?;
if let Some(encryption_key) = key {
vmgs.unlock_with_encryption_key(encryption_key)
.await
.map_err(|_| VmgsError::DecryptionFailed)?;
vmgs.write_file_encrypted(file_id, &buf)
.await
.map_err(|_| VmgsError::WriteFailed)?;
} else {
vmgs.write_file(file_id, &buf)
.await
.map_err(|_| VmgsError::WriteFailed)?;
}
Ok(())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn create_vmgs(
path: *const c_char,
file_size: u64,
force_create: bool,
encryption_key: *const c_char,
use_encryption: bool,
) -> VmgsError {
let path = unsafe {
if path.is_null() {
return VmgsError::NullParam;
}
match CStr::from_ptr(path).to_str() {
Ok(res) => res,
Err(_res) => return VmgsError::InvalidString,
}
};
let key = match use_encryption {
true => unsafe {
if encryption_key.is_null() {
return VmgsError::NullParam;
}
match CStr::from_ptr(encryption_key).to_str() {
Ok(res) => Some(res.as_bytes()),
Err(_res) => return VmgsError::InvalidString,
}
},
false => None,
};
let file_path = PathBuf::from(path);
match block_on(do_create(file_path, file_size, force_create, key)) {
Ok(_) => VmgsError::Ok,
Err(res) => res,
}
}
async fn do_create(
file_path: impl AsRef<Path>,
file_size: u64,
force_create: bool,
key: Option<&[u8]>,
) -> Result<(), VmgsError> {
let mut overwrite_existing_file = false;
if Path::new(file_path.as_ref()).exists() {
if force_create {
overwrite_existing_file = true;
} else {
return Err(VmgsError::FileExists);
}
}
if file_size != 0 && file_size < (VMGS_BYTES_PER_BLOCK * 4) as u64 || file_size % 512 != 0 {
return Err(VmgsError::InvalidFileSize);
}
let file_size = if file_size == 0 {
VMGS_DEFAULT_CAPACITY
} else {
file_size
};
let file = File::options()
.create(true)
.create_new(!overwrite_existing_file)
.truncate(true)
.read(true)
.write(true)
.open(&file_path)
.map_err(|_| VmgsError::FileDisk)?;
file.set_len(file_size).map_err(|_| VmgsError::FileDisk)?;
Vhd1Disk::make_fixed(&file).map_err(|_| VmgsError::FileDisk)?;
let disk = Vhd1Disk::open_fixed(file, false).map_err(|_| VmgsError::FileDisk)?;
let mut vmgs = Vmgs::format_new(Disk::new(disk).map_err(|_| VmgsError::FileDisk)?)
.await
.map_err(|_| VmgsError::InvalidVmgs)?;
if let Some(encryption_key) = key {
vmgs.add_new_encryption_key(encryption_key, EncryptionAlgorithm::AES_GCM)
.await
.map_err(|_| VmgsError::EncryptionFailed)?;
}
Ok(())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn query_size_vmgs(
path: *const c_char,
file_id: FileId,
out_size: *mut u64,
) -> VmgsError {
let path = unsafe {
if path.is_null() || out_size.is_null() {
return VmgsError::NullParam;
}
match CStr::from_ptr(path).to_str() {
Ok(res) => res,
Err(_res) => return VmgsError::InvalidString,
}
};
let size = match block_on(do_query_size(path, file_id)) {
Ok(value) => value,
Err(value) => return value,
};
unsafe { *out_size = size }
VmgsError::Ok
}
async fn do_query_size(file_path: &str, file_id: FileId) -> Result<u64, VmgsError> {
let vmgs = Vmgs::open(open_disk(file_path, true)?)
.await
.map_err(|_| VmgsError::InvalidVmgs)?;
let info = vmgs
.get_file_info(file_id)
.map_err(|_| VmgsError::FileInfoAllocated)?;
Ok(info.valid_bytes)
}