Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ feat_common_core = [
"ctrlaltdel",
"rev",
"setsid",
"last"
"last",
"dmesg"
]

[workspace.dependencies]
Expand Down Expand Up @@ -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"
Expand Down
18 changes: 18 additions & 0 deletions src/uu/dmesg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions src/uu/dmesg/dmesg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# dmesg

```
dmesg [options]
```

Display or control the kernel ring buffer.
153 changes: 153 additions & 0 deletions src/uu/dmesg/src/dmesg.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 142 additions & 0 deletions src/uu/dmesg/src/json.rs
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions src/uu/dmesg/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uucore::bin!(uu_dmesg);
Loading