extern crate alloc;
use alloc::vec::Vec;
use crate::io;
use crate::sys;
use crate::applets::get_arg;
pub fn dmesg(argc: i32, argv: *const *const u8) -> i32 {
let mut clear = false;
let mut raw = false;
let mut no_timestamp = false;
let mut human_time = false;
let mut i = 1i32;
while i < argc {
let Some(arg) = (unsafe { get_arg(argv, i) }) else {
i += 1;
continue;
};
if arg == b"-c" {
clear = true;
} else if arg == b"-r" {
raw = true;
} else if arg == b"-t" {
no_timestamp = true;
} else if arg == b"-T" {
human_time = true;
} else if arg == b"-n" {
i += 1;
} else if arg == b"-h" || arg == b"--help" {
io::write_str(1, b"Usage: dmesg [-c] [-r] [-t] [-T]\n");
io::write_str(1, b" -c Clear the ring buffer after printing\n");
io::write_str(1, b" -r Print raw message buffer\n");
io::write_str(1, b" -t Don't print timestamps\n");
io::write_str(1, b" -T Print human-readable timestamps\n");
return 0;
}
i += 1;
}
let fd = io::open(b"/dev/kmsg", libc::O_RDONLY | libc::O_NONBLOCK, 0);
if fd >= 0 {
unsafe { libc::lseek(fd, 0, 0) };
let mut line_buf = [0u8; 8192];
let mut boot_time: Option<i64> = None;
if human_time {
boot_time = get_boot_time();
}
loop {
let n = io::read(fd, &mut line_buf);
if n <= 0 {
break;
}
let line = &line_buf[..n as usize];
if raw {
io::write_all(1, line);
} else {
format_kmsg_line(line, no_timestamp, human_time, boot_time);
}
}
if clear {
}
io::close(fd);
return 0;
}
for path in &[b"/var/log/dmesg" as &[u8], b"/var/log/kern.log"] {
let fd = io::open(*path, libc::O_RDONLY, 0);
if fd >= 0 {
let mut buf = [0u8; 4096];
loop {
let n = io::read(fd, &mut buf);
if n <= 0 {
break;
}
io::write_all(1, &buf[..n as usize]);
}
io::close(fd);
return 0;
}
}
io::write_str(2, b"dmesg: cannot read kernel messages\n");
1
}
fn get_boot_time() -> Option<i64> {
let fd = io::open(b"/proc/stat", libc::O_RDONLY, 0);
if fd < 0 {
return None;
}
let mut buf = [0u8; 16384];
let n = io::read(fd, &mut buf);
io::close(fd);
if n <= 0 {
return None;
}
let content = &buf[..n as usize];
for line in content.split(|&c| c == b'\n') {
if line.starts_with(b"btime ") {
let time_str = &line[6..];
let end = time_str.iter().position(|&c| c < b'0' || c > b'9').unwrap_or(time_str.len());
let digits = &time_str[..end];
if !digits.is_empty() {
return sys::parse_u64(digits).map(|t| t as i64);
}
}
}
None
}
fn format_kmsg_line(line: &[u8], no_timestamp: bool, human_time: bool, boot_time: Option<i64>) {
let Some(semi_pos) = line.iter().position(|&c| c == b';') else {
io::write_all(1, line);
return;
};
let metadata = &line[..semi_pos];
let message = &line[semi_pos + 1..];
let fields: Vec<&[u8]> = metadata.split(|&c| c == b',').collect();
if fields.len() < 3 {
io::write_all(1, message);
return;
}
let timestamp_us = sys::parse_u64(fields[2]).unwrap_or(0);
if !no_timestamp {
if human_time {
if let Some(btime) = boot_time {
let abs_time = btime + (timestamp_us / 1_000_000) as i64;
format_human_time(abs_time);
} else {
format_relative_time(timestamp_us);
}
} else {
format_relative_time(timestamp_us);
}
io::write_str(1, b" ");
}
let msg = if message.ends_with(b"\n") {
&message[..message.len() - 1]
} else {
message
};
io::write_all(1, msg);
io::write_str(1, b"\n");
}
fn format_relative_time(timestamp_us: u64) {
let secs = timestamp_us / 1_000_000;
let usecs = timestamp_us % 1_000_000;
let mut buf = [0u8; 16];
io::write_str(1, b"[");
let sec_str = sys::format_u64(secs, &mut buf);
let padding = 5usize.saturating_sub(sec_str.len());
for _ in 0..padding {
io::write_str(1, b" ");
}
io::write_all(1, sec_str);
io::write_str(1, b".");
let usec_str = sys::format_u64(usecs, &mut buf);
let zeros = 6usize.saturating_sub(usec_str.len());
for _ in 0..zeros {
io::write_str(1, b"0");
}
io::write_all(1, usec_str);
io::write_str(1, b"]");
}
fn format_human_time(timestamp: i64) {
let mut tm: libc::tm = unsafe { core::mem::zeroed() };
let time_t = timestamp as i64;
unsafe { libc::localtime_r(&time_t, &mut tm) };
const MONTHS: [&[u8]; 12] = [
b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun",
b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec",
];
const DAYS: [&[u8]; 7] = [b"Sun", b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat"];
let mut buf = [0u8; 8];
io::write_str(1, b"[");
io::write_all(1, DAYS[tm.tm_wday as usize % 7]);
io::write_str(1, b" ");
io::write_all(1, MONTHS[tm.tm_mon as usize % 12]);
io::write_str(1, b" ");
let day = sys::format_u64(tm.tm_mday as u64, &mut buf);
if day.len() < 2 {
io::write_str(1, b" ");
}
io::write_all(1, day);
io::write_str(1, b" ");
let hour = sys::format_u64(tm.tm_hour as u64, &mut buf);
if hour.len() < 2 {
io::write_str(1, b"0");
}
io::write_all(1, hour);
io::write_str(1, b":");
let min = sys::format_u64(tm.tm_min as u64, &mut buf);
if min.len() < 2 {
io::write_str(1, b"0");
}
io::write_all(1, min);
io::write_str(1, b":");
let sec = sys::format_u64(tm.tm_sec as u64, &mut buf);
if sec.len() < 2 {
io::write_str(1, b"0");
}
io::write_all(1, sec);
io::write_str(1, b" ");
let year = sys::format_u64((tm.tm_year + 1900) as u64, &mut buf);
io::write_all(1, year);
io::write_str(1, b"]");
}
#[cfg(test)]
mod tests {
extern crate std;
use std::process::Command;
use std::path::PathBuf;
fn get_armybox_path() -> PathBuf {
if let Ok(path) = std::env::var("ARMYBOX_PATH") {
return PathBuf::from(path);
}
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::current_dir().unwrap());
let release = manifest_dir.join("target/release/armybox");
if release.exists() { return release; }
manifest_dir.join("target/debug/armybox")
}
#[test]
fn test_dmesg_help() {
let armybox = get_armybox_path();
if !armybox.exists() { return; }
let output = Command::new(&armybox)
.args(["dmesg", "-h"])
.output()
.unwrap();
assert_eq!(output.status.code(), Some(0));
assert!(output.stdout.starts_with(b"Usage:"));
}
}