Skip to content

Commit f271489

Browse files
authored
Merge pull request #152 from fuad1502/dmesg
Implement program `dmesg` with support for `--kmsg-file` and `--json` option.
2 parents 5f61d85 + d796cb5 commit f271489

File tree

11 files changed

+1017
-1
lines changed

11 files changed

+1017
-1
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ feat_common_core = [
3232
"ctrlaltdel",
3333
"rev",
3434
"setsid",
35-
"last"
35+
"last",
36+
"dmesg"
3637
]
3738

3839
[workspace.dependencies]
@@ -72,6 +73,7 @@ ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", pa
7273
rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" }
7374
setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src/uu/setsid" }
7475
last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" }
76+
dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg" }
7577

7678
[dev-dependencies]
7779
pretty_assertions = "1"

src/uu/dmesg/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "uu_dmesg"
3+
version = "0.0.1"
4+
edition = "2021"
5+
6+
[lib]
7+
path = "src/dmesg.rs"
8+
9+
[[bin]]
10+
name = "dmesg"
11+
path = "src/main.rs"
12+
13+
[dependencies]
14+
clap = { workspace = true }
15+
uucore = { workspace = true }
16+
regex = { workspace = true }
17+
serde_json = { workspace = true }
18+
serde = { workspace = true }

src/uu/dmesg/dmesg.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# dmesg
2+
3+
```
4+
dmesg [options]
5+
```
6+
7+
Display or control the kernel ring buffer.

src/uu/dmesg/src/dmesg.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// This file is part of the uutils util-linux package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use clap::{crate_version, Arg, ArgAction, Command};
7+
use regex::Regex;
8+
use std::fs;
9+
use uucore::{
10+
error::{FromIo, UResult, USimpleError},
11+
format_usage, help_about, help_usage,
12+
};
13+
14+
mod json;
15+
16+
const ABOUT: &str = help_about!("dmesg.md");
17+
const USAGE: &str = help_usage!("dmesg.md");
18+
19+
#[uucore::main]
20+
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
21+
let mut dmesg = Dmesg::new();
22+
let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
23+
if let Some(kmsg_file) = matches.get_one::<String>(options::KMSG_FILE) {
24+
dmesg.kmsg_file = kmsg_file;
25+
}
26+
if matches.get_flag(options::JSON) {
27+
dmesg.output_format = OutputFormat::Json;
28+
}
29+
dmesg.parse()?.print();
30+
Ok(())
31+
}
32+
33+
pub fn uu_app() -> Command {
34+
Command::new(uucore::util_name())
35+
.override_usage(format_usage(USAGE))
36+
.about(ABOUT)
37+
.version(crate_version!())
38+
.arg(
39+
Arg::new(options::KMSG_FILE)
40+
.short('K')
41+
.long("kmsg-file")
42+
.help("use the file in kmsg format")
43+
.action(ArgAction::Set),
44+
)
45+
.arg(
46+
Arg::new(options::JSON)
47+
.short('J')
48+
.long("json")
49+
.help("use JSON output format")
50+
.action(ArgAction::SetTrue),
51+
)
52+
}
53+
54+
mod options {
55+
pub const KMSG_FILE: &str = "kmsg-file";
56+
pub const JSON: &str = "json";
57+
}
58+
59+
struct Dmesg<'a> {
60+
kmsg_file: &'a str,
61+
output_format: OutputFormat,
62+
records: Option<Vec<Record>>,
63+
}
64+
65+
impl Dmesg<'_> {
66+
fn new() -> Self {
67+
Dmesg {
68+
kmsg_file: "/dev/kmsg",
69+
output_format: OutputFormat::Normal,
70+
records: None,
71+
}
72+
}
73+
74+
fn parse(mut self) -> UResult<Self> {
75+
let mut records = vec![];
76+
let re = Self::record_regex();
77+
let lines = self.read_lines_from_kmsg_file()?;
78+
for line in lines {
79+
for (_, [pri_fac, seq, time, msg]) in re.captures_iter(&line).map(|c| c.extract()) {
80+
records.push(Record::from_str_fields(
81+
pri_fac,
82+
seq,
83+
time,
84+
msg.to_string(),
85+
)?);
86+
}
87+
}
88+
self.records = Some(records);
89+
Ok(self)
90+
}
91+
92+
fn record_regex() -> Regex {
93+
let valid_number_pattern = "0|[1-9][0-9]*";
94+
let additional_fields_pattern = ",^[,;]*";
95+
let record_pattern = format!(
96+
"(?m)^({0}),({0}),({0}),.(?:{1})*;(.*)$",
97+
valid_number_pattern, additional_fields_pattern
98+
);
99+
Regex::new(&record_pattern).expect("invalid regex.")
100+
}
101+
102+
fn read_lines_from_kmsg_file(&self) -> UResult<Vec<String>> {
103+
let kmsg_bytes = fs::read(self.kmsg_file)
104+
.map_err_context(|| format!("cannot open {}", self.kmsg_file))?;
105+
let lines = kmsg_bytes
106+
.split(|&byte| byte == 0)
107+
.map(|line| String::from_utf8_lossy(line).to_string())
108+
.collect();
109+
Ok(lines)
110+
}
111+
112+
fn print(&self) {
113+
match self.output_format {
114+
OutputFormat::Json => self.print_json(),
115+
OutputFormat::Normal => unimplemented!(),
116+
}
117+
}
118+
119+
fn print_json(&self) {
120+
if let Some(records) = &self.records {
121+
println!("{}", json::serialize_records(records));
122+
}
123+
}
124+
}
125+
126+
enum OutputFormat {
127+
Normal,
128+
Json,
129+
}
130+
131+
struct Record {
132+
priority_facility: u32,
133+
_sequence: u64,
134+
timestamp_us: u64,
135+
message: String,
136+
}
137+
138+
impl Record {
139+
fn from_str_fields(pri_fac: &str, seq: &str, time: &str, msg: String) -> UResult<Record> {
140+
let pri_fac = str::parse(pri_fac);
141+
let seq = str::parse(seq);
142+
let time = str::parse(time);
143+
match (pri_fac, seq, time) {
144+
(Ok(pri_fac), Ok(seq), Ok(time)) => Ok(Record {
145+
priority_facility: pri_fac,
146+
_sequence: seq,
147+
timestamp_us: time,
148+
message: msg,
149+
}),
150+
_ => Err(USimpleError::new(1, "Failed to parse record field(s)")),
151+
}
152+
}
153+
}

src/uu/dmesg/src/json.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// This file is part of the uutils util-linux package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use serde::Serialize;
7+
use std::io;
8+
9+
pub fn serialize_records(records: &Vec<crate::Record>) -> String {
10+
let json = Dmesg::from(records);
11+
let formatter = DmesgFormatter::new();
12+
let mut buf = vec![];
13+
let mut serializer = serde_json::Serializer::with_formatter(&mut buf, formatter);
14+
json.serialize(&mut serializer).unwrap();
15+
String::from_utf8_lossy(&buf).to_string()
16+
}
17+
18+
#[derive(serde::Serialize)]
19+
struct Dmesg<'a> {
20+
dmesg: Vec<Record<'a>>,
21+
}
22+
23+
#[derive(serde::Serialize)]
24+
struct Record<'a> {
25+
pri: u32,
26+
time: u64,
27+
msg: &'a str,
28+
}
29+
30+
impl<'a> From<&'a Vec<crate::Record>> for Dmesg<'a> {
31+
fn from(value: &'a Vec<crate::Record>) -> Self {
32+
let mut dmesg_json = Dmesg { dmesg: vec![] };
33+
for record in value {
34+
let record_json = Record {
35+
pri: record.priority_facility,
36+
time: record.timestamp_us,
37+
msg: &record.message,
38+
};
39+
dmesg_json.dmesg.push(record_json);
40+
}
41+
dmesg_json
42+
}
43+
}
44+
45+
struct DmesgFormatter {
46+
nesting_depth: i32,
47+
}
48+
49+
impl DmesgFormatter {
50+
const SINGLE_INDENTATION: &[u8] = b" ";
51+
52+
fn new() -> Self {
53+
DmesgFormatter { nesting_depth: 0 }
54+
}
55+
56+
fn write_indentation<W>(&mut self, writer: &mut W) -> io::Result<()>
57+
where
58+
W: ?Sized + io::Write,
59+
{
60+
for _ in 0..self.nesting_depth {
61+
writer.write_all(Self::SINGLE_INDENTATION)?;
62+
}
63+
Ok(())
64+
}
65+
}
66+
67+
impl serde_json::ser::Formatter for DmesgFormatter {
68+
fn begin_object<W>(&mut self, writer: &mut W) -> io::Result<()>
69+
where
70+
W: ?Sized + io::Write,
71+
{
72+
self.nesting_depth += 1;
73+
writer.write_all(b"{\n")
74+
}
75+
76+
fn end_object<W>(&mut self, writer: &mut W) -> io::Result<()>
77+
where
78+
W: ?Sized + io::Write,
79+
{
80+
writer.write_all(b"\n")?;
81+
self.nesting_depth -= 1;
82+
self.write_indentation(writer)?;
83+
writer.write_all(b"}")
84+
}
85+
86+
fn begin_array<W>(&mut self, writer: &mut W) -> io::Result<()>
87+
where
88+
W: ?Sized + io::Write,
89+
{
90+
self.nesting_depth += 1;
91+
writer.write_all(b"[\n")
92+
}
93+
94+
fn end_array<W>(&mut self, writer: &mut W) -> io::Result<()>
95+
where
96+
W: ?Sized + io::Write,
97+
{
98+
writer.write_all(b"\n")?;
99+
self.nesting_depth -= 1;
100+
self.write_indentation(writer)?;
101+
writer.write_all(b"]")
102+
}
103+
104+
fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
105+
where
106+
W: ?Sized + io::Write,
107+
{
108+
if !first {
109+
writer.write_all(b",\n")?;
110+
}
111+
self.write_indentation(writer)
112+
}
113+
114+
fn begin_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
115+
where
116+
W: ?Sized + io::Write,
117+
{
118+
writer.write_all(b": ")
119+
}
120+
121+
fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
122+
where
123+
W: ?Sized + io::Write,
124+
{
125+
if first {
126+
self.write_indentation(writer)
127+
} else {
128+
writer.write_all(b",")
129+
}
130+
}
131+
132+
fn write_u64<W>(&mut self, writer: &mut W, value: u64) -> io::Result<()>
133+
where
134+
W: ?Sized + io::Write,
135+
{
136+
// The only u64 field in Dmesg is time, which requires a specific format
137+
let seconds = value / 1000000;
138+
let sub_seconds = value % 1000000;
139+
let repr = format!("{:>5}.{:0>6}", seconds, sub_seconds);
140+
writer.write_all(repr.as_bytes())
141+
}
142+
}

src/uu/dmesg/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uucore::bin!(uu_dmesg);

0 commit comments

Comments
 (0)