Merge pull request #152 from fuad1502/dmesg

Implement program `dmesg` with support for `--kmsg-file` and `--json` option.
This commit is contained in:
Daniel Hofstetter
2024-11-18 10:25:32 +01:00
committed by GitHub
11 changed files with 1017 additions and 1 deletions
Generated
+12
View File
@@ -880,6 +880,7 @@ dependencies = [
"tempfile",
"textwrap",
"uu_ctrlaltdel",
"uu_dmesg",
"uu_last",
"uu_lscpu",
"uu_lsmem",
@@ -898,6 +899,17 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_dmesg"
version = "0.0.1"
dependencies = [
"clap",
"regex",
"serde",
"serde_json",
"uucore",
]
[[package]]
name = "uu_last"
version = "0.0.1"
+3 -1
View File
@@ -32,7 +32,8 @@ feat_common_core = [
"ctrlaltdel",
"rev",
"setsid",
"last"
"last",
"dmesg"
]
[workspace.dependencies]
@@ -72,6 +73,7 @@ ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", pa
rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" }
setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src/uu/setsid" }
last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" }
dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg" }
[dev-dependencies]
pretty_assertions = "1"
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "uu_dmesg"
version = "0.0.1"
edition = "2021"
[lib]
path = "src/dmesg.rs"
[[bin]]
name = "dmesg"
path = "src/main.rs"
[dependencies]
clap = { workspace = true }
uucore = { workspace = true }
regex = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true }
+7
View File
@@ -0,0 +1,7 @@
# dmesg
```
dmesg [options]
```
Display or control the kernel ring buffer.
+153
View File
@@ -0,0 +1,153 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use clap::{crate_version, Arg, ArgAction, Command};
use regex::Regex;
use std::fs;
use uucore::{
error::{FromIo, UResult, USimpleError},
format_usage, help_about, help_usage,
};
mod json;
const ABOUT: &str = help_about!("dmesg.md");
const USAGE: &str = help_usage!("dmesg.md");
#[uucore::main]
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;
}
if matches.get_flag(options::JSON) {
dmesg.output_format = OutputFormat::Json;
}
dmesg.parse()?.print();
Ok(())
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.override_usage(format_usage(USAGE))
.about(ABOUT)
.version(crate_version!())
.arg(
Arg::new(options::KMSG_FILE)
.short('K')
.long("kmsg-file")
.help("use the file in kmsg format")
.action(ArgAction::Set),
)
.arg(
Arg::new(options::JSON)
.short('J')
.long("json")
.help("use JSON output format")
.action(ArgAction::SetTrue),
)
}
mod options {
pub const KMSG_FILE: &str = "kmsg-file";
pub const JSON: &str = "json";
}
struct Dmesg<'a> {
kmsg_file: &'a str,
output_format: OutputFormat,
records: Option<Vec<Record>>,
}
impl Dmesg<'_> {
fn new() -> Self {
Dmesg {
kmsg_file: "/dev/kmsg",
output_format: OutputFormat::Normal,
records: None,
}
}
fn parse(mut self) -> UResult<Self> {
let mut records = vec![];
let re = Self::record_regex();
let lines = self.read_lines_from_kmsg_file()?;
for line in lines {
for (_, [pri_fac, seq, time, msg]) in re.captures_iter(&line).map(|c| c.extract()) {
records.push(Record::from_str_fields(
pri_fac,
seq,
time,
msg.to_string(),
)?);
}
}
self.records = Some(records);
Ok(self)
}
fn record_regex() -> Regex {
let valid_number_pattern = "0|[1-9][0-9]*";
let additional_fields_pattern = ",^[,;]*";
let record_pattern = format!(
"(?m)^({0}),({0}),({0}),.(?:{1})*;(.*)$",
valid_number_pattern, additional_fields_pattern
);
Regex::new(&record_pattern).expect("invalid regex.")
}
fn read_lines_from_kmsg_file(&self) -> UResult<Vec<String>> {
let kmsg_bytes = fs::read(self.kmsg_file)
.map_err_context(|| format!("cannot open {}", self.kmsg_file))?;
let lines = kmsg_bytes
.split(|&byte| byte == 0)
.map(|line| String::from_utf8_lossy(line).to_string())
.collect();
Ok(lines)
}
fn print(&self) {
match self.output_format {
OutputFormat::Json => self.print_json(),
OutputFormat::Normal => unimplemented!(),
}
}
fn print_json(&self) {
if let Some(records) = &self.records {
println!("{}", json::serialize_records(records));
}
}
}
enum OutputFormat {
Normal,
Json,
}
struct Record {
priority_facility: u32,
_sequence: u64,
timestamp_us: u64,
message: String,
}
impl Record {
fn from_str_fields(pri_fac: &str, seq: &str, time: &str, msg: String) -> UResult<Record> {
let pri_fac = str::parse(pri_fac);
let seq = str::parse(seq);
let time = str::parse(time);
match (pri_fac, seq, time) {
(Ok(pri_fac), Ok(seq), Ok(time)) => Ok(Record {
priority_facility: pri_fac,
_sequence: seq,
timestamp_us: time,
message: msg,
}),
_ => Err(USimpleError::new(1, "Failed to parse record field(s)")),
}
}
}
+142
View File
@@ -0,0 +1,142 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use serde::Serialize;
use std::io;
pub fn serialize_records(records: &Vec<crate::Record>) -> String {
let json = Dmesg::from(records);
let formatter = DmesgFormatter::new();
let mut buf = vec![];
let mut serializer = serde_json::Serializer::with_formatter(&mut buf, formatter);
json.serialize(&mut serializer).unwrap();
String::from_utf8_lossy(&buf).to_string()
}
#[derive(serde::Serialize)]
struct Dmesg<'a> {
dmesg: Vec<Record<'a>>,
}
#[derive(serde::Serialize)]
struct Record<'a> {
pri: u32,
time: u64,
msg: &'a str,
}
impl<'a> From<&'a Vec<crate::Record>> for Dmesg<'a> {
fn from(value: &'a Vec<crate::Record>) -> Self {
let mut dmesg_json = Dmesg { dmesg: vec![] };
for record in value {
let record_json = Record {
pri: record.priority_facility,
time: record.timestamp_us,
msg: &record.message,
};
dmesg_json.dmesg.push(record_json);
}
dmesg_json
}
}
struct DmesgFormatter {
nesting_depth: i32,
}
impl DmesgFormatter {
const SINGLE_INDENTATION: &[u8] = b" ";
fn new() -> Self {
DmesgFormatter { nesting_depth: 0 }
}
fn write_indentation<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
for _ in 0..self.nesting_depth {
writer.write_all(Self::SINGLE_INDENTATION)?;
}
Ok(())
}
}
impl serde_json::ser::Formatter for DmesgFormatter {
fn begin_object<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.nesting_depth += 1;
writer.write_all(b"{\n")
}
fn end_object<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b"\n")?;
self.nesting_depth -= 1;
self.write_indentation(writer)?;
writer.write_all(b"}")
}
fn begin_array<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.nesting_depth += 1;
writer.write_all(b"[\n")
}
fn end_array<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b"\n")?;
self.nesting_depth -= 1;
self.write_indentation(writer)?;
writer.write_all(b"]")
}
fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
if !first {
writer.write_all(b",\n")?;
}
self.write_indentation(writer)
}
fn begin_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
writer.write_all(b": ")
}
fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
if first {
self.write_indentation(writer)
} else {
writer.write_all(b",")
}
}
fn write_u64<W>(&mut self, writer: &mut W, value: u64) -> io::Result<()>
where
W: ?Sized + io::Write,
{
// The only u64 field in Dmesg is time, which requires a specific format
let seconds = value / 1000000;
let sub_seconds = value % 1000000;
let repr = format!("{:>5}.{:0>6}", seconds, sub_seconds);
writer.write_all(repr.as_bytes())
}
}
+1
View File
@@ -0,0 +1 @@
uucore::bin!(uu_dmesg);
+32
View File
@@ -0,0 +1,32 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use crate::common::util::TestScenario;
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}
#[test]
fn test_kmsg_nonexistent_file() {
new_ucmd!()
.arg("--kmsg-file")
.arg("definitely-nonexistent-file")
.fails()
.code_is(1)
.no_stdout()
.stderr_is("dmesg: cannot open definitely-nonexistent-file: No such file or directory\n");
}
#[test]
fn test_kmsg_json() {
new_ucmd!()
.arg("--kmsg-file")
.arg("kmsg.input")
.arg("--json")
.run()
.no_stderr()
.stdout_is_templated_fixture("test_kmsg_json.expected", &[("\r\n", "\n")]);
}
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+4
View File
@@ -32,3 +32,7 @@ mod test_setsid;
#[cfg(feature = "last")]
#[path = "by-util/test_last.rs"]
mod test_last;
#[cfg(feature = "dmesg")]
#[path = "by-util/test_dmesg.rs"]
mod test_dmesg;