diff --git a/Cargo.lock b/Cargo.lock index ae2f810..65fac4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,6 +88,15 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "deranged" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +dependencies = [ + "powerfmt", +] + [[package]] name = "errno" version = "0.3.14" @@ -223,6 +232,12 @@ dependencies = [ "rustversion", ] +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + [[package]] name = "libc" version = "0.2.181" @@ -306,9 +321,16 @@ dependencies = [ "rand", "ringbuf", "rustc-hash", + "time", "tock-registers", ] +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + [[package]] name = "object" version = "0.38.1" @@ -375,6 +397,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -489,6 +517,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -546,6 +594,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tock-registers" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index dc857d5..f2ccb49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,9 @@ futures = { version = "0.3.31", default-features = false, features = ["alloc", " rand = { version = "0.9.2", default-features = false, features = ["small_rng"] } rustc-hash = { version = "2.1", default-features = false } +[build-dependencies] +time = { version = "0.3.47", features = ["formatting", "macros"] } # For build timestamping via build.rs + [features] default = ["smp"] # Support for Symmetric Multiprocessing @@ -44,3 +47,4 @@ codegen-units = 1 [lints] workspace = true + diff --git a/build.rs b/build.rs index 254baa1..6fa0de0 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,6 @@ use std::path::PathBuf; +use time::OffsetDateTime; +use time::macros::format_description; fn main() { let linker_script = match std::env::var("CARGO_CFG_TARGET_ARCH") { @@ -12,4 +14,15 @@ fn main() { println!("cargo::rerun-if-changed={}", linker_script.display()); println!("cargo::rustc-link-arg=-T{}", linker_script.display()); + + // Set an environment variable with the date and time of the build + let now = OffsetDateTime::now_utc(); + let format = format_description!( + "[weekday repr:short] [month repr:short] [day] [hour]:[minute]:[second] UTC [year]" + ); + let timestamp = now.format(&format).unwrap(); + #[cfg(feature = "smp")] + println!("cargo:rustc-env=MOSS_VERSION=#1 Moss SMP {timestamp}"); + #[cfg(not(feature = "smp"))] + println!("cargo:rustc-env=MOSS_VERSION=#1 Moss {timestamp}"); } diff --git a/src/kernel/uname.rs b/src/kernel/uname.rs index 30a1b2b..0589fac 100644 --- a/src/kernel/uname.rs +++ b/src/kernel/uname.rs @@ -4,10 +4,23 @@ use crate::{ memory::uaccess::{UserCopyable, copy_to_user}, }; use alloc::ffi::CString; +use core::ffi::CStr; +use core::ffi::c_char; use core::str::FromStr; -use core::{ffi::c_char, mem}; use libkernel::{error::Result, memory::address::TUA}; +const SYSNAME: &CStr = c"Moss"; + +/// Systemd uses the release field to determine compatibility. +/// It's also necessary for libc programs; otherwise they exit with an error Kernel too old. +const RELEASE: &CStr = c"4.2.3"; + +/// POSIX specifies the order when using -a (equivalent to -snrvm): +/// 1. sysname (-s) - OS name +/// 2. nodename (-n) - hostname +/// 3. release (-r) - OS release +/// 4. version (-v) - OS version +/// 5. machine (-m) - hardware type #[repr(C)] #[derive(Clone, Copy)] pub struct OldUtsname { @@ -18,6 +31,18 @@ pub struct OldUtsname { machine: [c_char; 65], } +impl Default for OldUtsname { + fn default() -> Self { + Self { + sysname: [0; 65], + nodename: [0; 65], + release: [0; 65], + version: [0; 65], + machine: [0; 65], + } + } +} + unsafe impl UserCopyable for OldUtsname {} fn copy_str_to_c_char_arr(dest: &mut [c_char], src: &[u8]) { @@ -33,29 +58,103 @@ fn copy_str_to_c_char_arr(dest: &mut [c_char], src: &[u8]) { // The rest of `dest` will remain zeroed from the initial `mem::zeroed`. } -pub async fn sys_uname(uts_ptr: TUA) -> Result { - let mut uts = unsafe { mem::zeroed::() }; +/// Build an `OldUtsname` struct with the current system information, without involving the +/// kernel. This makes it easier to test. +fn build_utsname() -> OldUtsname { + let mut uts = OldUtsname::default(); - let sysname = c"Moss".to_bytes_with_nul(); - copy_str_to_c_char_arr(&mut uts.sysname, sysname); + copy_str_to_c_char_arr(&mut uts.sysname, SYSNAME.to_bytes_with_nul()); let nodename = CString::from_str(&hostname().lock_save_irq()).unwrap(); copy_str_to_c_char_arr(&mut uts.nodename, nodename.as_c_str().to_bytes_with_nul()); - let release = c"4.2.3".to_bytes_with_nul(); - copy_str_to_c_char_arr(&mut uts.release, release); + copy_str_to_c_char_arr(&mut uts.release, RELEASE.to_bytes_with_nul()); - #[cfg(feature = "smp")] - let version = c"#1 Moss SMP Tue Feb 20 12:34:56 UTC 2024".to_bytes_with_nul(); - #[cfg(not(feature = "smp"))] - let version = c"#1 Moss Tue Feb 20 12:34:56 UTC 2024".to_bytes_with_nul(); - copy_str_to_c_char_arr(&mut uts.version, version); + let version = CString::from_str(env!("MOSS_VERSION")).unwrap(); + copy_str_to_c_char_arr(&mut uts.version, version.as_c_str().to_bytes_with_nul()); let machine = CString::new(ArchImpl::name()).unwrap(); let machine = machine.to_bytes_with_nul(); copy_str_to_c_char_arr(&mut uts.machine, machine); - copy_to_user(uts_ptr, uts).await?; + uts +} +/// Implement the uname syscall, returning 0 for success +pub async fn sys_uname(uts_ptr: TUA) -> Result { + let uts = build_utsname(); + copy_to_user(uts_ptr, uts).await?; Ok(0) } + +#[cfg(test)] +mod tests { + use crate::kernel::uname::{SYSNAME, build_utsname}; + use crate::ktest; + use core::ffi::CStr; + + ktest! { + fn sysname_correct() { + let uts = build_utsname(); + let sysname_cstr = unsafe { CStr::from_ptr(uts.sysname.as_ptr()) }; + assert_eq!(sysname_cstr, SYSNAME); + } + } + + fn validate_datetime(datetime: &str) { + let mut parts = datetime.splitn(6, ' '); + let day_of_week = parts.next().expect("Day of week"); // "Tue" + let month = parts.next().expect("Month"); // "Feb" + let day = parts.next().expect("Day"); // "20" + let time = parts.next().expect("Time"); // "12:34:56" + let timezone = parts.next().expect("TimeZone"); // "UTC" + let year = parts.next().expect("Year"); // "2024" + + assert_eq!(timezone, "UTC"); + assert!(year.parse::().is_ok()); + assert!(year.starts_with("20")); + assert!(time.split(':').all(|s| s.len() == 2)); + assert_eq!(month.len(), 3); + assert_eq!(day_of_week.len(), 3); + assert!(day.parse::().is_ok()); + } + + fn validate_version(version: &str, smp: bool) { + let mut parts = if smp { + version.splitn(4, ' ') + } else { + version.splitn(3, ' ') + }; + + let build_num = parts.next().unwrap(); // "#1" + assert!( + build_num.starts_with('#'), + "Build number should start with '#'" + ); + + let sysname = parts.next().unwrap(); // "Moss" + assert_eq!(sysname, "Moss"); + + if smp { + let smp = parts.next().unwrap(); // "SMP" + assert_eq!(smp, "SMP"); + } + + let datetime = parts.next().unwrap(); // "Tue Feb 20 12:34:56 UTC 2024" + validate_datetime(datetime) + } + + ktest! { + // Test that the version string is of the format "#1 Moss SMP Tue Feb 20 12:34:56 UTC 2024" + fn version_format_smp() { + let uts = build_utsname(); + let version_cstr = unsafe { CStr::from_ptr(uts.version.as_ptr()) }; + let version = version_cstr.to_str().unwrap(); + + #[cfg(feature = "smp")] + validate_version(version, true); + #[cfg(not(feature = "smp"))] + validate_version(version, false); + } + } +}