diff --git a/Cargo.lock b/Cargo.lock index 6f44ae0fb..814f20007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,6 +531,7 @@ dependencies = [ "log", "multiboot2", "pod", + "regex", "spin 0.9.8", "trapframe", "volatile", @@ -979,6 +980,31 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + [[package]] name = "ringbuf" version = "0.3.2" diff --git a/build/grub/conf/grub.cfg b/build/grub/conf/grub.cfg index c1a75a38c..96b80b5c6 100644 --- a/build/grub/conf/grub.cfg +++ b/build/grub/conf/grub.cfg @@ -2,7 +2,7 @@ set timeout_style=menu set timeout=1 menuentry 'jinux' { - multiboot2 /boot/jinux + multiboot2 /boot/jinux SHELL="/bin/sh" LOGNAME="root" HOME="/" USER="root" PATH="/bin" init=/usr/bin/busybox -- sh -l module2 --nounzip /boot/ramdisk.cpio.gz boot } diff --git a/framework/jinux-frame/Cargo.toml b/framework/jinux-frame/Cargo.toml index 0ec788b14..b7b623a88 100644 --- a/framework/jinux-frame/Cargo.toml +++ b/framework/jinux-frame/Cargo.toml @@ -18,12 +18,13 @@ log = "0.4" lazy_static = { version = "1.0", features = ["spin_no_std"] } trapframe = { git = "https://github.com/sdww0/trapframe-rs", rev = "e886763" } inherit-methods-macro = { git = "https://github.com/jinzhao-dev/inherit-methods-macro", rev = "98f7e3e" } -multiboot2 = "0.16.0" +regex = { version = "1.9.1", default-features = false, features = ["unicode"] } [target.x86_64-custom.dependencies] x86_64 = "0.14.2" x86 = "0.52.0" acpi = "4.1.1" aml = "0.16.3" +multiboot2 = { version = "0.16.0", features = [] } [features] diff --git a/framework/jinux-frame/src/arch/x86/boot/multiboot2.rs b/framework/jinux-frame/src/arch/x86/boot/multiboot2.rs index 36d15d8e3..725a1354c 100644 --- a/framework/jinux-frame/src/arch/x86/boot/multiboot2.rs +++ b/framework/jinux-frame/src/arch/x86/boot/multiboot2.rs @@ -1,9 +1,13 @@ -use alloc::{string::{ToString, String}, vec::Vec}; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; use multiboot2::{BootInformation, BootInformationHeader, MemoryAreaType}; use crate::boot::{ + kcmdline::KCmdlineArg, memory_region::{MemoryRegion, MemoryRegionType}, - kcmdline::KCmdlineArg, BootloaderAcpiArg, BootloaderFramebufferArg, + BootloaderAcpiArg, BootloaderFramebufferArg, }; use core::{arch::global_asm, mem::swap}; use spin::Once; @@ -35,9 +39,9 @@ fn init_kernel_commandline(kernel_cmdline: &'static Once) { .get() .unwrap() .command_line_tag() - .expect("Kernel commandline not found from the Multiboot2 header!") + .expect("Kernel command-line not found from the Multiboot2 header!") .cmdline() - .expect("UTF-8 error: failed to parse kernel commandline!") + .expect("UTF-8 error: failed to parse kernel command-line!") .into() }); } diff --git a/framework/jinux-frame/src/boot/kcmdline.rs b/framework/jinux-frame/src/boot/kcmdline.rs index 6bf93df51..c25c436a5 100644 --- a/framework/jinux-frame/src/boot/kcmdline.rs +++ b/framework/jinux-frame/src/boot/kcmdline.rs @@ -1,23 +1,189 @@ -//! The module to parse kernel commandline arguments. -//! +//! The module to parse kernel command-line arguments. +//! +//! The format of the Jinux command line string conforms +//! to the Linux kernel command line rules: +//! +//! https://www.kernel.org/doc/html/v6.4/admin-guide/kernel-parameters.html +//! -use alloc::string::String; +use alloc::{ + collections::BTreeMap, + ffi::CString, + string::{String, ToString}, + vec, + vec::Vec, +}; +use log::debug; +use regex::Regex; -/// The struct to store parsed kernel commandline arguments. +#[derive(PartialEq, Debug)] +struct InitprocArg { + // Since environment arguments can precede the init path argument, we + // have no choice but to wrap the path in `Option` and check it later. + path: Option, + argv: Vec, + envp: Vec, +} + +/// The struct to store the parsed kernel command-line arguments. +#[derive(Debug)] pub struct KCmdlineArg { - initproc: Option, + initproc: Option, + module_args: BTreeMap>, } // Define get APIs. impl KCmdlineArg { - pub fn get_initproc(&self) -> Option<&str> { - self.initproc.as_deref() + /// Get the path of the initprocess. + pub fn get_initproc_path(&self) -> Option<&str> { + self.initproc + .as_ref() + .and_then(|i| i.path.as_ref()) + .map(|s| s.as_str()) + } + /// Get the argument vector(argv) of the initprocess. + pub fn get_initproc_argv(&self) -> Option<&Vec> { + self.initproc.as_ref().map(|i| &i.argv) + } + /// Get the environment vector(envp) of the initprocess. + pub fn get_initproc_envp(&self) -> Option<&Vec> { + self.initproc.as_ref().map(|i| &i.argv) + } + /// Get the argument vector of a kernel module. + pub fn get_module_args(&self, module: &str) -> Option<&Vec> { + self.module_args.get(module) } } // Define the way to parse a string to `KCmdlineArg`. impl From<&str> for KCmdlineArg { fn from(cmdline: &str) -> Self { - KCmdlineArg { initproc: None } + // What we construct. + let mut result = KCmdlineArg { + initproc: None, + module_args: BTreeMap::new(), + }; + + // Split the command line string by spaces but preserve + // ones that are protected by double quotes(`"`). + let re = Regex::new(r#"((\S*"[^"]*"\S*)+|\S+)"#).unwrap(); + // Every thing after the "--" mark is the initproc arguments. + let mut kcmdline_end = false; + + // The main parse loop. The processing steps are arranged (not very strictly) + // by the analysis over the Backus–Naur form syntax tree. + for arg in re.find_iter(cmdline).map(|m| m.as_str()) { + if arg == "" || arg == " " { + continue; + } + // Cmdline => KernelArg "--" InitArg + // KernelArg => Arg "\s+" KernelArg | %empty + // InitArg => Arg "\s+" InitArg | %empty + if kcmdline_end { + if let Some(&mut ref mut i) = result.initproc.as_mut() { + i.argv.push(CString::new(arg).unwrap()); + } else { + panic!("Initproc arguments provided but no initproc path specified!"); + } + continue; + } + if arg == "--" { + kcmdline_end = true; + continue; + } + // Arg => Entry | Entry "=" Value + let arg_pattern: Vec<_> = arg.split("=").collect(); + let (entry, value) = match arg_pattern.len() { + 1 => (arg_pattern[0], None), + 2 => (arg_pattern[0], Some(arg_pattern[1])), + _ => { + panic!("Unable to parse argument {}", arg); + } + }; + // Entry => Module "." ModuleOptionName | KernelOptionName + let entry_pattern: Vec<_> = entry.split(".").collect(); + let (node, option) = match entry_pattern.len() { + 1 => (None, entry_pattern[0]), + 2 => (Some(entry_pattern[0]), entry_pattern[1]), + _ => { + panic!("Unable to parse entry {} in argument {}", entry, arg); + } + }; + if let Some(modname) = node { + let modarg = if let Some(v) = value { + CString::new(option.to_string() + "=" + v).unwrap() + } else { + CString::new(option).unwrap() + }; + result + .module_args + .entry(modname.to_string()) + .and_modify(|v| v.push(modarg.clone())) + .or_insert(vec![modarg.clone()]); + continue; + } + // KernelOptionName => /*literal string alternatives*/ | /*init environment*/ + if let Some(value) = value { + // The option has a value. + match option { + "init" => { + if let Some(&mut ref mut i) = result.initproc.as_mut() { + if let Some(v) = &i.path { + panic!("Initproc assigned twice in the command line!"); + } + i.path = Some(value.to_string()); + } else { + result.initproc = Some(InitprocArg { + path: Some(value.to_string()), + argv: Vec::new(), + envp: Vec::new(), + }); + } + } + _ => { + // If the option is not recognized, it is passed to the initproc. + // Pattern 'option=value' is treated as the init environment. + let envp_entry = CString::new(option.to_string() + "=" + value).unwrap(); + if let Some(&mut ref mut i) = result.initproc.as_mut() { + i.envp.push(envp_entry); + } else { + result.initproc = Some(InitprocArg { + path: None, + argv: Vec::new(), + envp: vec![envp_entry], + }); + } + } + } + } else { + // There is no value, the entry is only a option. + match option { + _ => { + // If the option is not recognized, it is passed to the initproc. + // Pattern 'option' without value is treated as the init argument. + let argv_entry = CString::new(option.to_string()).unwrap(); + if let Some(&mut ref mut i) = result.initproc.as_mut() { + i.argv.push(argv_entry); + } else { + result.initproc = Some(InitprocArg { + path: None, + argv: vec![argv_entry], + envp: Vec::new(), + }); + } + } + } + } + } + + debug!("{:?}", result); + + if let Some(&ref i) = result.initproc.as_ref() { + if i.path == None { + panic!("Initproc arguments provided but no initproc! Maybe have bad option."); + } + } + + result } } diff --git a/framework/jinux-frame/src/boot/mod.rs b/framework/jinux-frame/src/boot/mod.rs index c4c274c31..4345b12a7 100644 --- a/framework/jinux-frame/src/boot/mod.rs +++ b/framework/jinux-frame/src/boot/mod.rs @@ -1,6 +1,6 @@ //! The architecture-independent boot module, which provides a universal interface //! from the bootloader to the rest of the framework. -//! +//! use crate::arch::boot::init_boot_args;