Files
One-KVM/libs/ventoy-img-rs/src/resources.rs
mofeng-git d143d158e4 init
2025-12-28 18:19:16 +08:00

202 lines
5.9 KiB
Rust

//! Ventoy resources loader
//!
//! Loads Ventoy boot resources from external files in a resource directory.
//! Resource files (boot.img, core.img, ventoy.disk.img) should be pre-decompressed.
use crate::error::{Result, VentoyError};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
/// Resource file names
const BOOT_IMG_NAME: &str = "boot.img";
const CORE_IMG_NAME: &str = "core.img";
const VENTOY_DISK_IMG_NAME: &str = "ventoy.disk.img";
/// Ventoy signature (16 bytes at MBR offset 0x190)
pub const VENTOY_SIGNATURE: [u8; 16] = [
0x56, 0x54, 0x00, 0x47, 0x65, 0x00, 0x48, 0x44, 0x00, 0x52, 0x64, 0x00, 0x20, 0x45, 0x72, 0x0D,
];
/// Cached resources loaded from disk
struct ResourceCache {
boot_img: Vec<u8>,
core_img: Vec<u8>,
ventoy_disk_img: Vec<u8>,
}
/// Global resource cache
static RESOURCE_CACHE: OnceLock<ResourceCache> = OnceLock::new();
/// Initialize resources from a directory
///
/// This function must be called before using `get_boot_img()`, `get_core_img()`,
/// or `get_ventoy_disk_img()`. It loads all resource files into memory.
///
/// # Arguments
/// * `resource_dir` - Path to directory containing boot.img, core.img, ventoy.disk.img
///
/// # Example
/// ```no_run
/// use ventoy_img::resources::init_resources;
/// use std::path::Path;
///
/// init_resources(Path::new("/var/lib/one-kvm/ventoy")).unwrap();
/// ```
pub fn init_resources(resource_dir: &Path) -> Result<()> {
if RESOURCE_CACHE.get().is_some() {
// Already initialized
return Ok(());
}
let boot_path = resource_dir.join(BOOT_IMG_NAME);
let core_path = resource_dir.join(CORE_IMG_NAME);
let ventoy_disk_path = resource_dir.join(VENTOY_DISK_IMG_NAME);
// Check all files exist
if !boot_path.exists() {
return Err(VentoyError::ResourceNotFound(format!(
"boot.img not found at {}",
boot_path.display()
)));
}
if !core_path.exists() {
return Err(VentoyError::ResourceNotFound(format!(
"core.img not found at {}",
core_path.display()
)));
}
if !ventoy_disk_path.exists() {
return Err(VentoyError::ResourceNotFound(format!(
"ventoy.disk.img not found at {}",
ventoy_disk_path.display()
)));
}
// Load files
let boot_img = fs::read(&boot_path).map_err(|e| {
VentoyError::ResourceNotFound(format!("Failed to read {}: {}", boot_path.display(), e))
})?;
let core_img = fs::read(&core_path).map_err(|e| {
VentoyError::ResourceNotFound(format!("Failed to read {}: {}", core_path.display(), e))
})?;
let ventoy_disk_img = fs::read(&ventoy_disk_path).map_err(|e| {
VentoyError::ResourceNotFound(format!(
"Failed to read {}: {}",
ventoy_disk_path.display(),
e
))
})?;
// Validate boot.img size
if boot_img.len() != 512 {
return Err(VentoyError::ResourceNotFound(format!(
"boot.img has invalid size: {} bytes (expected 512)",
boot_img.len()
)));
}
let cache = ResourceCache {
boot_img,
core_img,
ventoy_disk_img,
};
// Try to set the cache (ignore if already set by another thread)
let _ = RESOURCE_CACHE.set(cache);
Ok(())
}
/// Check if resources have been initialized
pub fn is_initialized() -> bool {
RESOURCE_CACHE.get().is_some()
}
/// Get the boot.img data (512 bytes MBR boot code)
pub fn get_boot_img() -> Result<&'static [u8]> {
RESOURCE_CACHE
.get()
.map(|c| c.boot_img.as_slice())
.ok_or_else(|| {
VentoyError::ResourceNotFound(
"Resources not initialized. Call init_resources() first.".to_string(),
)
})
}
/// Get the core.img data (GRUB core image, ~1MB)
pub fn get_core_img() -> Result<&'static [u8]> {
RESOURCE_CACHE
.get()
.map(|c| c.core_img.as_slice())
.ok_or_else(|| {
VentoyError::ResourceNotFound(
"Resources not initialized. Call init_resources() first.".to_string(),
)
})
}
/// Get the ventoy.disk.img data (EFI partition, ~32MB)
pub fn get_ventoy_disk_img() -> Result<&'static [u8]> {
RESOURCE_CACHE
.get()
.map(|c| c.ventoy_disk_img.as_slice())
.ok_or_else(|| {
VentoyError::ResourceNotFound(
"Resources not initialized. Call init_resources() first.".to_string(),
)
})
}
/// Get the resource directory path for a given data directory
///
/// Returns `{data_dir}/ventoy`
pub fn get_resource_dir(data_dir: &Path) -> PathBuf {
data_dir.join("ventoy")
}
/// List required resource files
pub fn required_files() -> &'static [&'static str] {
&[BOOT_IMG_NAME, CORE_IMG_NAME, VENTOY_DISK_IMG_NAME]
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::TempDir;
fn create_test_resources(dir: &Path) {
// Create boot.img (512 bytes)
let mut boot = std::fs::File::create(dir.join(BOOT_IMG_NAME)).unwrap();
boot.write_all(&[0u8; 512]).unwrap();
// Create core.img (fake, 1KB)
let mut core = std::fs::File::create(dir.join(CORE_IMG_NAME)).unwrap();
core.write_all(&[0u8; 1024]).unwrap();
// Create ventoy.disk.img (fake, 1KB)
let mut ventoy = std::fs::File::create(dir.join(VENTOY_DISK_IMG_NAME)).unwrap();
ventoy.write_all(&[0u8; 1024]).unwrap();
}
#[test]
fn test_required_files() {
let files = required_files();
assert_eq!(files.len(), 3);
assert!(files.contains(&"boot.img"));
assert!(files.contains(&"core.img"));
assert!(files.contains(&"ventoy.disk.img"));
}
#[test]
fn test_get_resource_dir() {
let data_dir = Path::new("/var/lib/one-kvm");
let resource_dir = get_resource_dir(data_dir);
assert_eq!(resource_dir, PathBuf::from("/var/lib/one-kvm/ventoy"));
}
}