Merge pull request #227 from andrewdavidmackenzie/master

Rework uname to avoid hardcoded datetime and add unit tests

Restructure `uname` so that:
- there is an internal function that does the main logic and can be tested by unit tests
- Generate an env var in build.rs that is the actual build datetime timestamp
- Avoid hardcoded date in the version string
- Use the timestamp in the building of `uname` response, so the data is the actual build time
- add a simple unit test for the sysname field
- add more complex test for the version field that checks the date formatting (not exhaustively)

NOTE: The build timestamp can be used by any other function, syscall or part of the code if required.
This commit is contained in:
Andrew Mackenzie
2026-02-20 23:23:59 +01:00
committed by GitHub
parent 94018295da
commit b142fb74ec
4 changed files with 208 additions and 13 deletions

79
Cargo.lock generated
View File

@@ -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"

View File

@@ -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

View File

@@ -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}");
}

View File

@@ -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<OldUtsname>) -> Result<usize> {
let mut uts = unsafe { mem::zeroed::<OldUtsname>() };
/// 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<OldUtsname>) -> Result<usize> {
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::<u16>().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::<u8>().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);
}
}
}