dmesg: add support of reading from /dev/kmesg

In order to properly read from /dev/kmsg, we need [*]:
1) open /dev/kmsg with O_NONBLOCK
2) handle the EAGAIN/WouldBlock error as end of records
3) treat '\n' (and not '\0') as record separator.
4) do lseek(fd, 0, SEEK_DATA)

Because Windows doesn't support O_NONBLOCK and SEEK_DATA, we had protect
these code with #[cfg(not(target_os = "windows"))]. Moreover because
Windows doesn't have /dev/kmsg, it is mandatory to use the '-K' switch.

[*] https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg
This commit is contained in:
Goffredo Baroncelli 2025-01-01 18:29:20 +01:00
parent f878075a74
commit 98ef2d7f82

@ -9,15 +9,23 @@ use regex::Regex;
use std::{
collections::HashSet,
fs::File,
fs::OpenOptions,
hash::Hash,
io::{BufRead, BufReader},
io::{BufRead, BufReader, ErrorKind},
sync::OnceLock,
};
#[cfg(not(target_os = "windows"))]
use std::{os::fd::AsRawFd, os::unix::fs::OpenOptionsExt};
use uucore::{
error::{FromIo, UError, UResult, USimpleError},
error::{FromIo, UError, UIoError, UResult, USimpleError},
format_usage, help_about, help_usage,
};
#[cfg(not(target_os = "windows"))]
use uucore::libc;
mod json;
mod time_formatter;
@ -28,10 +36,6 @@ const USAGE: &str = help_usage!("dmesg.md");
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut dmesg = Dmesg::new();
let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
if let Some(kmsg_file) = matches.get_one::<String>(options::KMSG_FILE) {
dmesg.kmsg_file = kmsg_file;
dmesg.kmsg_record_separator = 0;
}
if matches.get_flag(options::JSON) {
dmesg.output_format = OutputFormat::Json;
}
@ -113,6 +117,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if let Some(until) = matches.get_one::<String>(options::UNTIL) {
dmesg.until_filter = Some(time_formatter::parse_datetime(until)?);
}
if let Some(kmsg_file) = matches.get_one::<String>(options::KMSG_FILE) {
dmesg.kmsg_file = kmsg_file;
dmesg.kmsg_record_separator = 0;
} else if cfg!(target_os = "windows") {
return Err(USimpleError::new(1, "Windows requires the use of '-K'"));
}
dmesg.print()?;
Ok(())
}
@ -198,7 +208,7 @@ impl Dmesg<'_> {
fn new() -> Self {
Dmesg {
kmsg_file: "/dev/kmsg",
kmsg_record_separator: 10, // '\n'
kmsg_record_separator: b'\n',
output_format: OutputFormat::Normal,
time_format: TimeFormat::Raw,
facility_filters: None,
@ -259,12 +269,26 @@ impl Dmesg<'_> {
}
fn try_iter(&self) -> UResult<RecordIterator> {
let file = File::open(self.kmsg_file)
let mut open_option = OpenOptions::new();
open_option.read(true);
#[cfg(not(target_os = "windows"))]
open_option.custom_flags(libc::O_NONBLOCK);
let file = open_option
.open(self.kmsg_file)
.map_err_context(|| format!("cannot open {}", self.kmsg_file))?;
#[cfg(not(target_os = "windows"))]
{
let fd = file.as_raw_fd();
unsafe { libc::lseek(fd, 0, libc::SEEK_DATA) };
}
let file_reader = BufReader::new(file);
Ok(RecordIterator {
file_reader,
kmsg_record_separator: self.kmsg_record_separator
kmsg_record_separator: self.kmsg_record_separator,
})
}
@ -386,10 +410,20 @@ impl Iterator for RecordIterator {
impl RecordIterator {
fn read_record_line(&mut self) -> UResult<Option<String>> {
let mut buf = vec![];
let num_bytes = self.file_reader.read_until(self.kmsg_record_separator, &mut buf)?;
match num_bytes {
0 => Ok(None),
_ => Ok(Some(String::from_utf8_lossy(&buf).to_string())),
match self
.file_reader
.read_until(self.kmsg_record_separator, &mut buf)
{
/*
* - a read(2) from /dev/kmsg returns WouldBlock if there aren't
* any new record
* - a read(2) from a file returns 0 if the we reached the end
* In these cases return Ok(None)
*/
Ok(0) => Ok(None),
Ok(_) => Ok(Some(String::from_utf8_lossy(&buf).to_string())),
Err(e) if e.kind() == ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(Box::new(UIoError::from(e))),
}
}