use std::{env, error::Error, fs, path::Path};
use clap::{Args, Parser, Subcommand};
use crate::{
AxVMCrateConfig,
templates::{VmTemplateParams, get_vm_config_template},
};
#[derive(Parser)]
#[command(name = "axvmconfig")]
#[command(about = "A simple VM configuration tool for ArceOS-Hypervisor.", long_about = None)]
#[command(args_conflicts_with_subcommands = true)]
#[command(flatten_help = true)]
pub struct Cli {
#[command(subcommand)]
pub subcmd: CLISubCmd,
}
#[derive(Subcommand)]
#[command(args_conflicts_with_subcommands = true)]
#[command(flatten_help = true)]
pub enum CLISubCmd {
Check(CheckArgs),
Generate(TemplateArgs),
}
#[derive(Debug, Args)]
pub struct CheckArgs {
#[arg(short, long)]
config_path: String,
}
#[derive(Debug, Args)]
pub struct TemplateArgs {
#[arg(short = 'a', long)]
arch: String,
#[arg(short = 'i', long, default_value_t = 0)]
id: usize,
#[arg(short = 'n', long, default_value_t = String::from("GuestVM"))]
name: String,
#[arg(short = 't', long, default_value_t = 1)]
vm_type: usize,
#[arg(short = 'c', long, default_value_t = 1)]
cpu_num: usize,
#[arg(short = 'e', long, default_value_t = 1)]
entry_point: usize,
#[arg(short = 'k', long)]
kernel_path: String,
#[arg(short = 'l', long, value_parser = parse_usize)]
kernel_load_addr: usize,
#[arg(long, default_value_t = String::from("fs"))]
image_location: String,
#[arg(long)]
cmdline: Option<String>,
#[arg(short = 'O', long, value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
output: Option<std::path::PathBuf>,
}
fn parse_usize(s: &str) -> Result<usize, Box<dyn Error + Send + Sync + 'static>> {
if let Some(stripped) = s.strip_prefix("0x") {
Ok(usize::from_str_radix(stripped, 16)?)
} else if let Some(stripped) = s.strip_prefix("0b") {
Ok(usize::from_str_radix(stripped, 2)?)
} else {
Ok(s.parse()?)
}
}
pub fn run() {
let cli = Cli::parse();
match cli.subcmd {
CLISubCmd::Check(args) => {
let file_path = &args.config_path;
if !Path::new(file_path).exists() {
eprintln!("Error: File '{}' does not exist.", file_path);
std::process::exit(1);
}
let file_content = match fs::read_to_string(file_path) {
Ok(content) => content,
Err(err) => {
eprintln!("Error: Failed to read file '{}': {}", file_path, err);
std::process::exit(1);
}
};
match AxVMCrateConfig::from_toml(&file_content) {
Ok(config) => {
println!("Config file '{}' is valid.", file_path);
println!("Config: {:#x?}", config);
}
Err(err) => {
eprintln!("Error: Config file '{}' is invalid: {}", file_path, err);
std::process::exit(1);
}
}
}
CLISubCmd::Generate(args) => {
let kernel_path = if args.image_location == "memory" {
Path::new(&args.kernel_path)
.canonicalize()
.unwrap()
.to_str()
.unwrap()
.to_string()
} else {
args.kernel_path.clone()
};
let template = get_vm_config_template(VmTemplateParams {
id: args.id,
name: args.name + "-" + args.arch.as_str(),
vm_type: args.vm_type,
cpu_num: args.cpu_num,
entry_point: args.entry_point,
kernel_path,
kernel_load_addr: args.kernel_load_addr,
image_location: args.image_location,
cmdline: args.cmdline,
});
let template_toml = toml::to_string(&template).unwrap();
let target_path = match args.output {
Some(relative_path) => relative_path,
None => env::current_dir().unwrap().join("template.toml"),
};
match fs::write(&target_path, template_toml) {
Ok(_) => {
println!("Template file '{:?}' has been generated.", target_path);
}
Err(err) => {
eprintln!(
"Error: Failed to write template file '{:?}': {}",
target_path, err
);
std::process::exit(1);
}
}
}
}
}