mirror of
https://github.com/uutils/util-linux.git
synced 2026-06-10 16:13:52 -07:00
Merge pull request #152 from fuad1502/dmesg
Implement program `dmesg` with support for `--kmsg-file` and `--json` option.
This commit is contained in:
Generated
+12
@@ -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
@@ -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"
|
||||
|
||||
@@ -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 }
|
||||
@@ -0,0 +1,7 @@
|
||||
# dmesg
|
||||
|
||||
```
|
||||
dmesg [options]
|
||||
```
|
||||
|
||||
Display or control the kernel ring buffer.
|
||||
@@ -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)")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uucore::bin!(uu_dmesg);
|
||||
@@ -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")]);
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+645
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user