From a9c47ed256abed5cc86959c5ce2564adfcfa337f Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Fri, 23 Jan 2026 16:41:17 +0100 Subject: [PATCH 01/53] Makefile: use machine type for uname We are interested in machine architecture and want to get: ``` $ uname -m aarch64 $ uname -m x86_64 ``` Signed-off-by: Petr Hodina --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 63f9c58..02c959e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .DEFAULT_GOAL := build .PHONY:build microhop-release-static microhop-debug-static microgen-release microgen-debug _reset_placeholder -ARCH := $(shell uname -p) +ARCH := $(shell uname -m) ARC_VERSION := $(shell cat src/microhop.rs | grep 'static VERSION' | sed -e 's/.*=//g' -e 's/[" ;]//g') ARC_NAME := microhop-${ARC_VERSION} From cd9a704541bf04b88c811e9e587804a65446306f Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Fri, 23 Jan 2026 16:45:09 +0100 Subject: [PATCH 02/53] kmodprobe: Handle missing module gracefuly Signed-off-by: Petr Hodina --- src/kmodprobe.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/kmodprobe.rs b/src/kmodprobe.rs index a99b8e4..dc40b3c 100644 --- a/src/kmodprobe.rs +++ b/src/kmodprobe.rs @@ -40,7 +40,13 @@ impl KModProbe { /// Load a kernel module pub fn modprobe(&self, name: &str) { let mp: PathBuf = if !name.contains('/') || !name.contains('.') { - self.find_module(name).unwrap_or_default() + match self.find_module(name) { + Some(path) => path, + None => { + log::error!("Kernel module {} not found in {}", name, self.km_path.display()); + return; + } + } } else { self.km_path.join(name) }; From 82c024661df156459ee7649ca63be0678bbb85fb Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Mon, 26 Jan 2026 00:52:15 +0100 Subject: [PATCH 03/53] cargo: use 'fn_params_layout' due to 'fn_args_layout' being deprecated Signed-off-by: Petr Hodina --- rustfmt.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index 1e342b0..e930e7e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -19,5 +19,5 @@ tab_spaces = 4 # Perks use_field_init_shorthand = true -fn_args_layout = "Compressed" +fn_params_layout = "Compressed" use_try_shorthand = true From 278b7063501ca5dda8a3814d756cea28b54faf9e Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Fri, 23 Jan 2026 17:45:05 +0100 Subject: [PATCH 04/53] microgen: Add kernel config validation Signed-off-by: Petr Hodina --- microgen/src/kconfig_validator.rs | 221 ++++++++++++++++++++++++++++++ microgen/src/main.rs | 1 + 2 files changed, 222 insertions(+) create mode 100644 microgen/src/kconfig_validator.rs diff --git a/microgen/src/kconfig_validator.rs b/microgen/src/kconfig_validator.rs new file mode 100644 index 0000000..31bda03 --- /dev/null +++ b/microgen/src/kconfig_validator.rs @@ -0,0 +1,221 @@ +use colored::Colorize; +use std::{ + collections::HashMap, + fs::File, + io::{BufRead, BufReader, Error}, + path::Path, +}; + +pub struct KConfigValidator { + config: HashMap, +} + +impl KConfigValidator { + pub fn from_file>(path: P) -> Result { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut config = HashMap::new(); + + for line in reader.lines() { + let line = line?; + let trimmed = line.trim(); + + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + + if let Some(eq_pos) = trimmed.find('=') { + let key = trimmed[..eq_pos].trim().to_string(); + let value = trimmed[eq_pos + 1..].trim().to_string(); + config.insert(key, value); + } + } + + Ok(KConfigValidator { config }) + } + + pub fn is_enabled(&self, option: &str) -> bool { + let option = if option.starts_with("CONFIG_") { option.to_string() } else { format!("CONFIG_{}", option) }; + + match self.config.get(&option) { + Some(val) => val == "y" || val == "m", + None => false, + } + } + + pub fn is_module(&self, option: &str) -> bool { + let option = if option.starts_with("CONFIG_") { option.to_string() } else { format!("CONFIG_{}", option) }; + + match self.config.get(&option) { + Some(val) => val == "m", + None => false, + } + } + + pub fn get_value(&self, option: &str) -> Option<&String> { + let option = if option.starts_with("CONFIG_") { option.to_string() } else { format!("CONFIG_{}", option) }; + + self.config.get(&option) + } + + pub fn validate(&self, mh_config: &profile::cfg::MhConfig) -> Result { + let mut result = ValidationResult::new(); + + let modules = mh_config.get_modules(); + if !modules.is_empty() { + if !self.is_enabled("MODULES") { + result.add_error( + "CONFIG_MODULES", + "Kernel module support is disabled, but microhop.conf requires modules to be loaded", + ); + } else { + result.add_info("CONFIG_MODULES", "Kernel module support is enabled"); + } + } + + for module_name in modules { + self.validate_module(module_name, &mut result); + } + + if let Ok(disks) = mh_config.get_disks() { + for disk in disks { + let fstype = disk.get_fstype().to_uppercase(); + self.validate_filesystem(&fstype, &mut result); + } + } + + self.validate_base_features(&mut result); + + Ok(result) + } + + fn validate_module(&self, module_name: &str, result: &mut ValidationResult) { + let config_name = module_name.to_uppercase().replace('-', "_"); + + if self.is_module(&config_name) { + result.add_success(&config_name, &format!("Module {} can be loaded", module_name)); + } else if self.is_enabled(&config_name) { + result.add_warning( + &config_name, + &format!( + "Module {} is built-in (=y) instead of loadable module (=m). It will be available but cannot be loaded dynamically", + module_name + ), + ); + } else { + result.add_error( + &config_name, + &format!("Module {} is not enabled in kernel config. Required by microhop.conf", module_name), + ); + } + } + + fn validate_filesystem(&self, fstype: &str, result: &mut ValidationResult) { + let config_name = format!("{}_FS", fstype); + + if self.is_enabled(&config_name) { + result.add_success(&config_name, &format!("Filesystem {} is supported", fstype)); + } else { + result.add_error(&config_name, &format!("Filesystem {} is not enabled. Required by microhop.conf", fstype)); + } + } + + fn validate_base_features(&self, result: &mut ValidationResult) { + let required_features = vec![ + ("BLK_DEV_INITRD", "Initial RAM filesystem support"), + ("DEVTMPFS", "Maintain a devtmpfs filesystem"), + ("TMPFS", "Tmpfs virtual memory filesystem"), + ("PROC_FS", "Proc filesystem"), + ("SYSFS", "Sysfs filesystem"), + ]; + + for (feature, description) in required_features { + if self.is_enabled(feature) { + result.add_info(feature, &format!("{} is enabled", description)); + } else { + result.add_warning(feature, &format!("{} is not enabled. This may cause boot issues", description)); + } + } + } +} + +#[derive(Debug)] +pub struct ValidationResult { + errors: Vec<(String, String)>, + warnings: Vec<(String, String)>, + successes: Vec<(String, String)>, + info: Vec<(String, String)>, +} + +impl ValidationResult { + pub fn new() -> Self { + ValidationResult { errors: Vec::new(), warnings: Vec::new(), successes: Vec::new(), info: Vec::new() } + } + + pub fn add_error(&mut self, option: &str, message: &str) { + self.errors.push((option.to_string(), message.to_string())); + } + + pub fn add_warning(&mut self, option: &str, message: &str) { + self.warnings.push((option.to_string(), message.to_string())); + } + + pub fn add_success(&mut self, option: &str, message: &str) { + self.successes.push((option.to_string(), message.to_string())); + } + + pub fn add_info(&mut self, option: &str, message: &str) { + self.info.push((option.to_string(), message.to_string())); + } + + pub fn has_errors(&self) -> bool { + !self.errors.is_empty() + } + + pub fn has_warnings(&self) -> bool { + !self.warnings.is_empty() + } + + pub fn print(&self) { + println!("\n{}", "=== Kernel Configuration Validation ===".bright_cyan().bold()); + + if !self.info.is_empty() { + println!("\n{}", "Information:".bright_blue().bold()); + for (option, msg) in &self.info { + println!(" {} {}", "ℹ".bright_blue(), format!("[{}] {}", option, msg).white()); + } + } + + if !self.warnings.is_empty() { + println!("\n{}", "Warnings:".bright_yellow().bold()); + for (option, msg) in &self.warnings { + println!(" {} {}", "⚠".bright_yellow(), format!("[{}] {}", option, msg).yellow()); + } + } + + if !self.errors.is_empty() { + println!("\n{}", "Errors:".bright_red().bold()); + for (option, msg) in &self.errors { + println!(" {} {}", "✗".bright_red(), format!("[{}] {}", option, msg).red()); + } + } + + println!("\n{}", "=== Validation Summary ===".bright_cyan().bold()); + println!( + " {} {} | {} {}", + "Errors:".bright_red().bold(), + self.errors.len().to_string().bright_red(), + "Warnings:".bright_yellow().bold(), + self.warnings.len().to_string().bright_yellow() + ); + + if self.has_errors() { + println!("\n{}", "Validation FAILED - kernel configuration is incompatible".bright_red().bold()); + } else if self.has_warnings() { + println!("\n{}", "Validation passed with warnings - check configuration".bright_yellow().bold()); + } else { + println!("\n{}", "Validation PASSED - kernel configuration is compatible".bright_green().bold()); + } + println!(); + } +} diff --git a/microgen/src/main.rs b/microgen/src/main.rs index dccd48d..2179314 100644 --- a/microgen/src/main.rs +++ b/microgen/src/main.rs @@ -1,5 +1,6 @@ mod analyser; mod clidef; +mod kconfig_validator; mod rdgen; mod rdpack; From 16f61a8f7c7495f31059c3f6d60d07c51e9a2c4e Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Fri, 23 Jan 2026 16:57:23 +0100 Subject: [PATCH 05/53] microgen: Call kconfig validation during initramfs creation Signed-off-by: Petr Hodina --- microgen/src/clidef.rs | 14 +++++++ microgen/src/main.rs | 85 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/microgen/src/clidef.rs b/microgen/src/clidef.rs index b6ab6bd..3d58f01 100644 --- a/microgen/src/clidef.rs +++ b/microgen/src/clidef.rs @@ -41,6 +41,20 @@ pub fn clidef(version: &'static str, appname: &'static str) -> Command { .conflicts_with_all(["extract"]) .help("Path to the initramfs configuration (profile)"), ) + .arg( + Arg::new("kernel-config") + .long("kernel-config") + .value_name("PATH") + .help("Path to kernel .config file for validation against microhop.conf requirements"), + ) + .arg( + Arg::new("validate-only") + .long("validate-only") + .action(clap::ArgAction::SetTrue) + .requires("kernel-config") + .requires("config") + .help("Only validate kernel config against microhop.conf, don't generate initramfs"), + ) .arg( Arg::new("extract") .short('x') diff --git a/microgen/src/main.rs b/microgen/src/main.rs index 2179314..ada5c28 100644 --- a/microgen/src/main.rs +++ b/microgen/src/main.rs @@ -58,6 +58,9 @@ fn run_new(params: &ArgMatches) -> Result<(), Box> { let x_mods: Vec = params.get_many::("extract").unwrap_or_default().map(|s| s.to_string()).collect(); let k_info = kmoddep::get_kernel_infos(Some(params.get_one::("root").unwrap())); let profile = params.get_one::("config"); + let kernel_config = params.get_one::("kernel-config"); + let validate_only = params.get_flag("validate-only"); + if let Err(k_info) = k_info { println!("Unable to get the information about the kernel: {}", k_info); return Ok(()); @@ -73,22 +76,74 @@ fn run_new(params: &ArgMatches) -> Result<(), Box> { } } else if let Some(profile) = profile { let cfg = profile::cfg::get_mh_config(Some(profile))?; - if let Ok(k_info) = k_info { - let kfo: KernelInfo; - - // Rewrite this better - if k_info.len() > 1 { - panic!("Need to implement matching a proper kernel from CLI") - } else { - kfo = k_info[0].to_owned(); + + if let Some(kconfig_path) = kernel_config { + println!("{}", "Validating kernel configuration...".bright_cyan().bold()); + match kconfig_validator::KConfigValidator::from_file(kconfig_path) { + Ok(validator) => { + match validator.validate(&cfg) { + Ok(result) => { + result.print(); + + if validate_only { + // Exit after validation + if result.has_errors() { + return Err(Box::new(io::Error::new( + io::ErrorKind::InvalidData, + "Kernel configuration validation failed", + ))); + } + return Ok(()); + } + + if result.has_errors() { + return Err(Box::new(io::Error::new( + io::ErrorKind::InvalidData, + "Cannot generate initramfs: kernel configuration is incompatible", + ))); + } + + if result.has_warnings() { + println!("{}", "⚠ Continuing with warnings...".bright_yellow()); + } + } + Err(e) => { + eprintln!("{}", format!("Validation error: {}", e).bright_red()); + return Err(Box::new(e)); + } + } + } + Err(e) => { + eprintln!("{}", format!("Failed to parse kernel config: {}", e).bright_red()); + return Err(Box::new(e)); + } + } + } else if validate_only { + return Err(Box::new(io::Error::new( + io::ErrorKind::InvalidInput, + "--validate-only requires --kernel-config to be specified", + ))); + } + + // Generate initramfs if not validation-only + if !validate_only { + if let Ok(k_info) = k_info { + let kfo: KernelInfo; + + // Rewrite this better + if k_info.len() > 1 { + panic!("Need to implement matching a proper kernel from CLI") + } else { + kfo = k_info[0].to_owned(); + } + println!("Generating initramfs"); + IrfsGen::generate( + &kfo, + cfg, + PathBuf::from(params.get_one::("output").unwrap()), + PathBuf::from(params.get_one::("file").unwrap()), + )?; } - println!("Generating initramfs"); - IrfsGen::generate( - &kfo, - cfg, - PathBuf::from(params.get_one::("output").unwrap()), - PathBuf::from(params.get_one::("file").unwrap()), - )?; } } else { clidef::clidef(VERSION, APPNAME).print_help().unwrap(); From 9547ac87270a64d0454bd6a6b178f150e1d9f7ed Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Fri, 23 Jan 2026 21:07:50 +0100 Subject: [PATCH 06/53] microgen: Validate better filesystems and block devices Signed-off-by: Petr Hodina --- microgen/src/clidef.rs | 23 ++++- microgen/src/kconfig_validator.rs | 143 ++++++++++++++++++++++++++++-- microgen/src/main.rs | 22 ++++- 3 files changed, 176 insertions(+), 12 deletions(-) diff --git a/microgen/src/clidef.rs b/microgen/src/clidef.rs index 3d58f01..feac1a6 100644 --- a/microgen/src/clidef.rs +++ b/microgen/src/clidef.rs @@ -23,9 +23,14 @@ pub fn clidef(version: &'static str, appname: &'static str) -> Command { .long("list") .value_name("PATH") .help("List available kernel versions in a given root filesystem") - .conflicts_with_all(["lsmod"]), + .conflicts_with_all(["lsmod", "filesystems", "block-devices"]), ) - .arg(Arg::new("lsmod").short('m').long("lsmod").action(clap::ArgAction::SetTrue).help("Just a fancy lsmod")), + .arg(Arg::new("lsmod").short('m').long("lsmod").action(clap::ArgAction::SetTrue).help("Just a fancy lsmod") + .conflicts_with_all(["filesystems", "block-devices"])) + .arg(Arg::new("filesystems").long("filesystems").action(clap::ArgAction::SetTrue) + .help("Show supported filesystems and their kernel config options")) + .arg(Arg::new("block-devices").long("block-devices").action(clap::ArgAction::SetTrue) + .help("Show supported block devices and their kernel config options")), ) .subcommand(Command::new("analyse").about("Analyse current system and generate a profile from it")) .subcommand( @@ -55,6 +60,20 @@ pub fn clidef(version: &'static str, appname: &'static str) -> Command { .requires("config") .help("Only validate kernel config against microhop.conf, don't generate initramfs"), ) + .arg( + Arg::new("filesystems") + .long("filesystems") + .value_name("FS_TYPES") + .help("Comma-separated list of filesystem types to validate (e.g., squashfs,ext4). If not specified, filesystem validation will issue warnings only") + .value_delimiter(','), + ) + .arg( + Arg::new("block-devices") + .long("block-devices") + .value_name("BLK_TYPES") + .help("Comma-separated list of block device types to validate (e.g., virtio_blk,nvme). If not specified, block device validation will issue warnings only") + .value_delimiter(','), + ) .arg( Arg::new("extract") .short('x') diff --git a/microgen/src/kconfig_validator.rs b/microgen/src/kconfig_validator.rs index 31bda03..99ce9a3 100644 --- a/microgen/src/kconfig_validator.rs +++ b/microgen/src/kconfig_validator.rs @@ -6,6 +6,59 @@ use std::{ path::Path, }; +pub const SUPPORTED_FILESYSTEMS: &[(&str, &str, &str)] = &[ + ("ext4", "CONFIG_EXT4_FS", "Fourth Extended Filesystem"), + ("ext2", "CONFIG_EXT2_FS", "Second Extended Filesystem"), + ("squashfs", "CONFIG_SQUASHFS", "Compressed read-only filesystem"), + ("btrfs", "CONFIG_BTRFS_FS", "B-tree filesystem"), + ("xfs", "CONFIG_XFS_FS", "SGI XFS filesystem"), + ("f2fs", "CONFIG_F2FS_FS", "Flash-Friendly File System"), + ("jffs2", "CONFIG_JFFS2_FS", "Journalling Flash File System v2"), + ("ubifs", "CONFIG_UBIFS_FS", "UBIFS file system"), + ("tmpfs", "CONFIG_TMPFS", "Temporary filesystem"), + ("iso9660", "CONFIG_ISO9660_FS", "ISO9660 filesystem"), + ("vfat", "CONFIG_VFAT_FS", "VFAT filesystem"), + ("ntfs", "CONFIG_NTFS_FS", "NTFS filesystem"), +]; + +pub const SUPPORTED_BLOCK_DEVICES: &[(&str, &str, &str)] = &[ + ("virtio_blk", "CONFIG_VIRTIO_BLK", "Virtio block driver"), + ("virtio_mmio", "CONFIG_VIRTIO_MMIO", "Platform bus driver for memory mapped virtio devices"), + ("nvme", "CONFIG_BLK_DEV_NVME", "NVM Express block device"), + ("usb_storage", "CONFIG_USB_STORAGE", "USB Mass Storage support"), + ("uas", "CONFIG_USB_UAS", "USB Attached SCSI"), + ("sd_mod", "CONFIG_BLK_DEV_SD", "SCSI disk support"), + ("sr_mod", "CONFIG_BLK_DEV_SR", "SCSI CDROM support"), + ("mmc_block", "CONFIG_MMC_BLOCK", "MMC block device driver"), + ("nand_block", "CONFIG_MTD_NAND_CORE", "MTD NAND core device driver"), + ("sdhci", "CONFIG_MMC_SDHCI", "Secure Digital Host Controller Interface support"), + ("ahci", "CONFIG_SATA_AHCI", "AHCI SATA support"), + ("nvme", "CONFIG_BLK_DEV_NVME", "NVME support"), + ("ufs", "CONFIG_SCSI_UFSHCD", "UFS support"), + ("ata_piix", "CONFIG_ATA_PIIX", "Intel PIIX/ICH SATA support"), + ("loop", "CONFIG_BLK_DEV_LOOP", "Loopback device support"), +]; + +pub fn print_supported_filesystems() { + println!("\n{}", "=== Supported Filesystems ===".bright_cyan().bold()); + println!("{}", "Use these names with --filesystems option during validation\n".white()); + + for (name, config, desc) in SUPPORTED_FILESYSTEMS { + println!(" {:<15} {:<25} {}", name.bright_green().bold(), config.bright_yellow(), desc.white()); + } + println!(); +} + +pub fn print_supported_block_devices() { + println!("\n{}", "=== Supported Block Devices ===".bright_cyan().bold()); + println!("{}", "Use these names with --block-devices option during validation\n".white()); + + for (name, config, desc) in SUPPORTED_BLOCK_DEVICES { + println!(" {:<20} {:<30} {}", name.bright_green().bold(), config.bright_yellow(), desc.white()); + } + println!(); +} + pub struct KConfigValidator { config: HashMap, } @@ -58,9 +111,18 @@ impl KConfigValidator { self.config.get(&option) } - pub fn validate(&self, mh_config: &profile::cfg::MhConfig) -> Result { + /// Validate kernel configuration against microhop requirements + /// + /// # Arguments + /// * `mh_config` - The microhop configuration to validate against + /// * `filesystems` - Optional list of filesystem types to validate. If None, filesystem checks will be warnings only. + /// * `block_devices` - Optional list of block device types to validate. If None, block device checks will be warnings only. + pub fn validate( + &self, mh_config: &profile::cfg::MhConfig, filesystems: Option<&[String]>, block_devices: Option<&[String]>, + ) -> Result { let mut result = ValidationResult::new(); + // Check if CONFIG_MODULES is enabled if modules are required let modules = mh_config.get_modules(); if !modules.is_empty() { if !self.is_enabled("MODULES") { @@ -77,10 +139,22 @@ impl KConfigValidator { self.validate_module(module_name, &mut result); } - if let Ok(disks) = mh_config.get_disks() { + if let Some(fs_list) = filesystems { + for fstype in fs_list { + let fstype_upper = fstype.to_uppercase(); + self.validate_filesystem(&fstype_upper, &mut result, true); + } + } else if let Ok(disks) = mh_config.get_disks() { for disk in disks { let fstype = disk.get_fstype().to_uppercase(); - self.validate_filesystem(&fstype, &mut result); + self.validate_filesystem(&fstype, &mut result, false); + } + } + + if let Some(blk_list) = block_devices { + for blktype in blk_list { + let blktype_upper = blktype.to_uppercase(); + self.validate_block_device(&blktype_upper, &mut result, true); } } @@ -110,13 +184,66 @@ impl KConfigValidator { } } - fn validate_filesystem(&self, fstype: &str, result: &mut ValidationResult) { - let config_name = format!("{}_FS", fstype); + fn validate_filesystem(&self, fstype: &str, result: &mut ValidationResult, is_error: bool) { + let fstype_lower = fstype.to_lowercase(); + let fs_entry = SUPPORTED_FILESYSTEMS.iter().find(|(name, config, _)| { + name.eq_ignore_ascii_case(&fstype_lower) || config.trim_start_matches("CONFIG_").eq_ignore_ascii_case(fstype) + }); - if self.is_enabled(&config_name) { - result.add_success(&config_name, &format!("Filesystem {} is supported", fstype)); + if let Some((fs_name, config_name, _)) = fs_entry { + let config_option = config_name.trim_start_matches("CONFIG_"); + if self.is_enabled(config_option) { + result.add_success(config_option, &format!("Filesystem {} is supported", fs_name)); + } else { + let msg = if is_error { + format!("Filesystem {} is not enabled. Required for specified rootfs", fs_name) + } else { + format!("Filesystem {} is not validated (use --filesystems to validate)", fs_name) + }; + + if is_error { + result.add_error(config_option, &msg); + } else { + result.add_warning(config_option, &msg); + } + } + } else { + result.add_error(fstype, &format!("Filesystem {} is not enabled. Required by microhop.conf", fstype)); + } + } + + fn validate_block_device(&self, blktype: &str, result: &mut ValidationResult, is_error: bool) { + let blktype_lower = blktype.to_lowercase(); + let blk_entry = SUPPORTED_BLOCK_DEVICES.iter().find(|(name, config, _)| { + name.eq_ignore_ascii_case(&blktype_lower) + || config.trim_start_matches("CONFIG_").eq_ignore_ascii_case(blktype) + || config.trim_start_matches("CONFIG_").replace('_', "-").eq_ignore_ascii_case(blktype) + }); + + if let Some((blk_name, config_name, _)) = blk_entry { + let config_option = config_name.trim_start_matches("CONFIG_"); + if self.is_enabled(config_option) { + result.add_success(config_option, &format!("Block device {} is supported", blk_name)); + } else { + let msg = if is_error { + format!("Block device {} is not enabled. Required for specified block device", blk_name) + } else { + format!("Block device {} is not validated (use --block-devices to validate)", blk_name) + }; + + if is_error { + result.add_error(config_option, &msg); + } else { + result.add_warning(config_option, &msg); + } + } } else { - result.add_error(&config_name, &format!("Filesystem {} is not enabled. Required by microhop.conf", fstype)); + let msg = format!("Unknown block device type '{}'. Use --list-block-devices to see supported types", blktype); + if is_error { + result.add_error(blktype, &msg); + } else { + result.add_warning(blktype, &msg); + } } } diff --git a/microgen/src/main.rs b/microgen/src/main.rs index ada5c28..1710d4a 100644 --- a/microgen/src/main.rs +++ b/microgen/src/main.rs @@ -13,9 +13,19 @@ use std::{error::Error, io, path::PathBuf}; static VERSION: &str = "0.1.0"; static APPNAME: &str = "microgen"; -/// Run information section fn run_info(params: &ArgMatches) -> Result<(), Box> { let rfs = params.get_one::("list").map(|v| v.as_str()); + + if params.get_flag("filesystems") { + kconfig_validator::print_supported_filesystems(); + return Ok(()); + } + + if params.get_flag("block-devices") { + kconfig_validator::print_supported_block_devices(); + return Ok(()); + } + let k_info = kmoddep::get_kernel_infos(rfs)?; if rfs.is_some() { @@ -79,9 +89,17 @@ fn run_new(params: &ArgMatches) -> Result<(), Box> { if let Some(kconfig_path) = kernel_config { println!("{}", "Validating kernel configuration...".bright_cyan().bold()); + + let filesystems: Option> = + params.get_many::("filesystems").map(|values| values.map(|s| s.to_string()).collect()); + + // Get optional block devices list from CLI + let block_devices: Option> = + params.get_many::("block-devices").map(|values| values.map(|s| s.to_string()).collect()); + match kconfig_validator::KConfigValidator::from_file(kconfig_path) { Ok(validator) => { - match validator.validate(&cfg) { + match validator.validate(&cfg, filesystems.as_deref(), block_devices.as_deref()) { Ok(result) => { result.print(); From fb3978c1a1dc67c34b94ebe39ee8cfc14456ba84 Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Fri, 23 Jan 2026 22:44:16 +0100 Subject: [PATCH 07/53] microhop: Add cmdline parser Signed-off-by: Petr Hodina --- src/cmdline.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 2 files changed, 43 insertions(+) create mode 100644 src/cmdline.rs diff --git a/src/cmdline.rs b/src/cmdline.rs new file mode 100644 index 0000000..d879441 --- /dev/null +++ b/src/cmdline.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; +use std::fs; +use std::io::Error; + +pub struct CmdLine { + params: HashMap, +} + +impl CmdLine { + pub fn new() -> Result { + let cmdline = fs::read_to_string("/proc/cmdline")?; + let mut params = HashMap::new(); + + for param in cmdline.split_whitespace() { + if let Some((key, value)) = param.split_once('=') { + params.insert(key.to_string(), value.to_string()); + } else { + params.insert(param.to_string(), String::new()); + } + } + + Ok(CmdLine { params }) + } + + pub fn get_root_device(&self) -> Option<&str> { + self.params.get("root").map(|s| s.as_str()) + } + + pub fn get_root_fstype(&self) -> Option<&str> { + self.params.get("rootfstype").map(|s| s.as_str()) + } + + pub fn get_root_options(&self) -> Option<&str> { + self.params.get("rootflags").map(|s| s.as_str()) + } +} + +impl Default for CmdLine { + fn default() -> Self { + Self::new().unwrap_or_else(|_| CmdLine { params: HashMap::new() }) + } +} diff --git a/src/main.rs b/src/main.rs index 57186e7..2950ea1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod cmdline; mod kmodprobe; mod logger; mod microhop; From 2ba4ea5a520cb95e4e149e549a7b76cd98b7d3fb Mon Sep 17 00:00:00 2001 From: Petr Hodina Date: Fri, 23 Jan 2026 22:51:47 +0100 Subject: [PATCH 08/53] microhop: Support uuid, label and /dev as rootfs Signed-off-by: Petr Hodina --- src/microhop.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/microhop.rs b/src/microhop.rs index 825f35f..d0a3d1a 100644 --- a/src/microhop.rs +++ b/src/microhop.rs @@ -58,6 +58,39 @@ pub fn mount_fs>(filesystems: &[SystemDir]) { } } +/// Parse device specification and resolve to device path +/// Supports: uuid=, label=