From 2f803ceae38cac7b0114ad5fc2ed138c054b54dc Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sat, 7 Dec 2024 08:57:01 +0700 Subject: [PATCH 01/22] dmesg: implement RecordIterator. --- src/uu/dmesg/src/dmesg.rs | 55 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 75bfe8f..fc60d94 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -5,7 +5,10 @@ use clap::{crate_version, Arg, ArgAction, Command}; use regex::Regex; -use std::fs; +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, +}; use uucore::{ error::{FromIo, UResult, USimpleError}, format_usage, help_about, help_usage, @@ -194,6 +197,56 @@ enum TimeFormat { Raw, } +struct RecordIterator { + file_reader: BufReader<File>, +} + +impl Iterator for RecordIterator { + type Item = UResult<Record>; + + fn next(&mut self) -> Option<Self::Item> { + match self.read_record_line() { + Err(e) => Some(Err(e)), + Ok(None) => None, + Ok(Some(line)) => match self.parse_record(&line) { + None => self.next(), + Some(record) => Some(Ok(record)), + }, + } + } +} + +impl RecordIterator { + fn read_record_line(&mut self) -> UResult<Option<String>> { + let mut buf = vec![]; + let num_bytes = self.file_reader.read_until(0, &mut buf)?; + match num_bytes { + 0 => Ok(None), + _ => Ok(Some(String::from_utf8_lossy(&buf).to_string())), + } + } + + fn parse_record(&self, record_line: &str) -> Option<Record> { + Self::record_regex() + .captures_iter(record_line) + .map(|c| c.extract()) + .filter_map(|(_, [pri_fac, seq, time, msg])| { + Record::from_str_fields(pri_fac, seq, time, msg.to_string()).ok() + }) + .next() + } + + 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.") + } +} + struct Record { priority_facility: u32, _sequence: u64, From 6adabf4e7d1d22cb2fe43f173a8e6a1f6ab0337b Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sat, 7 Dec 2024 09:22:24 +0700 Subject: [PATCH 02/22] dmesg: use RecordIterator for Dmesg print. --- src/uu/dmesg/src/dmesg.rs | 107 +++++++++++++------------------------- 1 file changed, 37 insertions(+), 70 deletions(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index fc60d94..0e53f84 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -6,7 +6,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use regex::Regex; use std::{ - fs::{self, File}, + fs::File, io::{BufRead, BufReader}, }; use uucore::{ @@ -46,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }; } - dmesg.parse()?.print(); + dmesg.print()?; Ok(()) } @@ -90,7 +90,6 @@ struct Dmesg<'a> { kmsg_file: &'a str, output_format: OutputFormat, time_format: TimeFormat, - records: Option<Vec<Record>>, } impl Dmesg<'_> { @@ -99,87 +98,55 @@ impl Dmesg<'_> { kmsg_file: "/dev/kmsg", output_format: OutputFormat::Normal, time_format: TimeFormat::Raw, - 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) { + fn print(&self) -> UResult<()> { match self.output_format { OutputFormat::Json => self.print_json(), OutputFormat::Normal => self.print_normal(), } } - fn print_json(&self) { - if let Some(records) = &self.records { - println!("{}", json::serialize_records(records)); - } + fn print_json(&self) -> UResult<()> { + let records: UResult<Vec<Record>> = self.try_iter()?.collect(); + println!("{}", json::serialize_records(&records?)); + Ok(()) } - fn print_normal(&self) { - if let Some(records) = &self.records { - let mut reltime_formatter = time_formatter::ReltimeFormatter::new(); - let mut delta_formatter = time_formatter::DeltaFormatter::new(); - for record in records { - match self.time_format { - TimeFormat::Delta => { - print!("[{}] ", delta_formatter.format(record.timestamp_us)) - } - TimeFormat::Reltime => { - print!("[{}] ", reltime_formatter.format(record.timestamp_us)) - } - TimeFormat::Ctime => { - print!("[{}] ", time_formatter::ctime(record.timestamp_us)) - } - TimeFormat::Iso => { - print!("{} ", time_formatter::iso(record.timestamp_us)) - } - TimeFormat::Raw => { - print!("[{}] ", time_formatter::raw(record.timestamp_us)) - } - TimeFormat::Notime => (), + fn print_normal(&self) -> UResult<()> { + let mut reltime_formatter = time_formatter::ReltimeFormatter::new(); + let mut delta_formatter = time_formatter::DeltaFormatter::new(); + for record in self.try_iter()? { + let record = record?; + match self.time_format { + TimeFormat::Delta => { + print!("[{}] ", delta_formatter.format(record.timestamp_us)) } - println!("{}", record.message); + TimeFormat::Reltime => { + print!("[{}] ", reltime_formatter.format(record.timestamp_us)) + } + TimeFormat::Ctime => { + print!("[{}] ", time_formatter::ctime(record.timestamp_us)) + } + TimeFormat::Iso => { + print!("{} ", time_formatter::iso(record.timestamp_us)) + } + TimeFormat::Raw => { + print!("[{}] ", time_formatter::raw(record.timestamp_us)) + } + TimeFormat::Notime => (), } + println!("{}", record.message); } + Ok(()) + } + + fn try_iter(&self) -> UResult<RecordIterator> { + let file = File::open(self.kmsg_file) + .map_err_context(|| format!("cannot open {}", self.kmsg_file))?; + let file_reader = BufReader::new(file); + Ok(RecordIterator { file_reader }) } } From db9b3433bcb06668d8386b836b586b1a7c74a01d Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sat, 7 Dec 2024 15:28:29 +0000 Subject: [PATCH 03/22] tests/dmesg: add facility and level filter tests. --- tests/by-util/test_dmesg.rs | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index b811584..b6cfb73 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -81,3 +81,68 @@ fn test_invalid_time_format() { .code_is(1) .stderr_only("dmesg: unknown time format: definitely-invalid\n"); } + +#[test] +fn test_filter_facility() { + let facilities = [ + "kern", "user", "mail", "daemon", "auth", "syslog", "lpr", "news", "uucp", "cron", + "authpriv", "ftp", "local0", "local1", "local2", "local3", "local4", "local5", "local6", + "local7", + ]; + for facility in facilities { + let facility_filter_arg = format!("--facility={facility}"); + let mut cmd = new_ucmd!(); + let result = cmd + .arg("--kmsg-file") + .arg("kmsg.input") + .arg(facility_filter_arg) + .succeeds(); + let stdout = result.no_stderr().stdout_str(); + assert_eq!(stdout.lines().count(), 8); + let expected = format!("LOG_{}", facility.to_uppercase()); + stdout + .lines() + .for_each(|line| assert!(line.contains(&expected))); + } +} + +#[test] +fn test_filter_levels() { + let levels = [ + "emerg", "alert", "crit", "err", "warn", "notice", "info", "debug", + ]; + for level in levels { + let level_filter_arg = format!("--level={level}"); + let mut cmd = new_ucmd!(); + let result = cmd + .arg("--kmsg-file") + .arg("kmsg.input") + .arg(level_filter_arg) + .succeeds(); + let stdout = result.no_stderr().stdout_str(); + assert_eq!(stdout.lines().count(), 20); + let expected = format!("LOG_{}", level.to_uppercase()); + stdout + .lines() + .for_each(|line| assert!(line.contains(&expected))); + } +} + +#[test] +fn test_filter_multiple() { + let mut cmd = new_ucmd!(); + let result = cmd + .arg("--kmsg-file") + .arg("kmsg.input") + .arg("--facility=kern,user") + .arg("--level=emerg,alert") + .succeeds(); + let stdout = result.no_stderr().stdout_str(); + assert_eq!(stdout.lines().count(), 4); + stdout.lines().for_each(|line| { + assert!( + (line.contains("LOG_KERN") || line.contains("LOG_USER")) + && (line.contains("LOG_EMERG") || line.contains("LOG_ALERT")) + ) + }); +} From db42f300b016252338cb084309514b1770754dca Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sat, 7 Dec 2024 17:24:55 +0000 Subject: [PATCH 04/22] dmesg: add facility & level argument. --- src/uu/dmesg/src/dmesg.rs | 117 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 0e53f84..056f112 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -6,6 +6,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use regex::Regex; use std::{ + collections::HashSet, fs::File, io::{BufRead, BufReader}, }; @@ -46,6 +47,62 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }; } + if let Some(list_args) = matches.get_many::<String>(options::FACILITY) { + let mut facility_filters = HashSet::new(); + for list in list_args { + for arg in list.split(',') { + let facility = match arg { + "kern" => Facility::Kern, + "user" => Facility::User, + "mail" => Facility::Mail, + "daemon" => Facility::Daemon, + "auth" => Facility::Auth, + "syslog" => Facility::Syslog, + "lpr" => Facility::Lpr, + "news" => Facility::News, + "uucp" => Facility::Uucp, + "cron" => Facility::Cron, + "authpriv" => Facility::Authpriv, + "ftp" => Facility::Ftp, + "res0" => Facility::Res0, + "res1" => Facility::Res1, + "res2" => Facility::Res2, + "res3" => Facility::Res3, + "local0" => Facility::Local0, + "local1" => Facility::Local1, + "local2" => Facility::Local2, + "local3" => Facility::Local3, + "local4" => Facility::Local4, + "local5" => Facility::Local5, + "local6" => Facility::Local6, + "local7" => Facility::Local7, + _ => return Err(USimpleError::new(1, format!("unknown facility '{arg}'"))), + }; + facility_filters.insert(facility); + } + } + dmesg.facility_filters = Some(facility_filters); + } + if let Some(list_args) = matches.get_many::<String>(options::LEVEL) { + let mut level_filters = HashSet::new(); + for list in list_args { + for arg in list.split(',') { + let level = match arg { + "emerg" => Level::Emerg, + "alert" => Level::Alert, + "crit" => Level::Crit, + "err" => Level::Err, + "warn" => Level::Warn, + "notice" => Level::Notice, + "info" => Level::Info, + "debug" => Level::Debug, + _ => return Err(USimpleError::new(1, format!("unknown level '{arg}'"))), + }; + level_filters.insert(level); + } + } + dmesg.level_filters = Some(level_filters); + } dmesg.print()?; Ok(()) } @@ -78,18 +135,36 @@ pub fn uu_app() -> Command { ) .action(ArgAction::Set), ) + .arg( + Arg::new(options::FACILITY) + .short('f') + .long("facility") + .help("restrict output to defined facilities") + .action(ArgAction::Append), + ) + .arg( + Arg::new(options::LEVEL) + .short('l') + .long("level") + .help("restrict output to defined levels") + .action(ArgAction::Append), + ) } mod options { pub const KMSG_FILE: &str = "kmsg-file"; pub const JSON: &str = "json"; pub const TIME_FORMAT: &str = "time-format"; + pub const FACILITY: &str = "facility"; + pub const LEVEL: &str = "level"; } struct Dmesg<'a> { kmsg_file: &'a str, output_format: OutputFormat, time_format: TimeFormat, + facility_filters: Option<HashSet<Facility>>, + level_filters: Option<HashSet<Level>>, } impl Dmesg<'_> { @@ -98,6 +173,8 @@ impl Dmesg<'_> { kmsg_file: "/dev/kmsg", output_format: OutputFormat::Normal, time_format: TimeFormat::Raw, + facility_filters: None, + level_filters: None, } } @@ -164,6 +241,46 @@ enum TimeFormat { Raw, } +#[derive(Eq, Hash, PartialEq)] +enum Facility { + Kern, + User, + Mail, + Daemon, + Auth, + Syslog, + Lpr, + News, + Uucp, + Cron, + Authpriv, + Ftp, + Res0, + Res1, + Res2, + Res3, + Local0, + Local1, + Local2, + Local3, + Local4, + Local5, + Local6, + Local7, +} + +#[derive(Eq, Hash, PartialEq)] +enum Level { + Emerg, + Alert, + Crit, + Err, + Warn, + Notice, + Info, + Debug, +} + struct RecordIterator { file_reader: BufReader<File>, } From e5ee286c7a306f38073b07526d23b7d4628ad5fc Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sun, 8 Dec 2024 12:29:47 +0000 Subject: [PATCH 05/22] dmesg: implement level & facility filters. --- src/uu/dmesg/src/dmesg.rs | 89 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 056f112..fdcf15f 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -8,10 +8,11 @@ use regex::Regex; use std::{ collections::HashSet, fs::File, + hash::Hash, io::{BufRead, BufReader}, }; use uucore::{ - error::{FromIo, UResult, USimpleError}, + error::{FromIo, UError, UResult, USimpleError}, format_usage, help_about, help_usage, }; @@ -186,7 +187,7 @@ impl Dmesg<'_> { } fn print_json(&self) -> UResult<()> { - let records: UResult<Vec<Record>> = self.try_iter()?.collect(); + let records: UResult<Vec<Record>> = self.try_filtered_iter()?.collect(); println!("{}", json::serialize_records(&records?)); Ok(()) } @@ -194,7 +195,7 @@ impl Dmesg<'_> { fn print_normal(&self) -> UResult<()> { let mut reltime_formatter = time_formatter::ReltimeFormatter::new(); let mut delta_formatter = time_formatter::DeltaFormatter::new(); - for record in self.try_iter()? { + for record in self.try_filtered_iter()? { let record = record?; match self.time_format { TimeFormat::Delta => { @@ -219,12 +220,40 @@ impl Dmesg<'_> { Ok(()) } + fn try_filtered_iter(&self) -> UResult<Box<dyn Iterator<Item = UResult<Record>> + '_>> { + Ok(match (&self.facility_filters, &self.level_filters) { + (None, None) => Box::new(self.try_iter()?), + (None, Some(set)) => Box::new(self.try_iter()?.filter(Self::is_record_in_set(set))), + (Some(set), None) => Box::new(self.try_iter()?.filter(Self::is_record_in_set(set))), + (Some(set_1), Some(set_2)) => Box::new( + self.try_iter()? + .filter(Self::is_record_in_set(set_1)) + .filter(Self::is_record_in_set(set_2)), + ), + }) + } + fn try_iter(&self) -> UResult<RecordIterator> { let file = File::open(self.kmsg_file) .map_err_context(|| format!("cannot open {}", self.kmsg_file))?; let file_reader = BufReader::new(file); Ok(RecordIterator { file_reader }) } + + fn is_record_in_set<T>( + set: &HashSet<T>, + ) -> impl Fn(&Result<Record, Box<dyn UError>>) -> bool + '_ + where + T: TryFrom<u32> + Eq + Hash, + { + |record: &UResult<Record>| match record { + Ok(record) => match T::try_from(record.priority_facility) { + Ok(t) => set.contains(&t), + Err(_) => true, + }, + Err(_) => true, + } + } } enum OutputFormat { @@ -354,3 +383,57 @@ impl Record { } } } + +impl TryFrom<u32> for Level { + type Error = Box<dyn UError>; + + fn try_from(value: u32) -> UResult<Self> { + let priority = value & 0b111; + match priority { + 0 => Ok(Level::Emerg), + 1 => Ok(Level::Alert), + 2 => Ok(Level::Crit), + 3 => Ok(Level::Err), + 4 => Ok(Level::Warn), + 5 => Ok(Level::Notice), + 6 => Ok(Level::Info), + 7 => Ok(Level::Debug), + _ => todo!(), + } + } +} + +impl TryFrom<u32> for Facility { + type Error = Box<dyn UError>; + + fn try_from(value: u32) -> Result<Self, Self::Error> { + let facility = (value >> 3) as u8; + match facility { + 0 => Ok(Facility::Kern), + 1 => Ok(Facility::User), + 2 => Ok(Facility::Mail), + 3 => Ok(Facility::Daemon), + 4 => Ok(Facility::Auth), + 5 => Ok(Facility::Syslog), + 6 => Ok(Facility::Lpr), + 7 => Ok(Facility::News), + 8 => Ok(Facility::Uucp), + 9 => Ok(Facility::Cron), + 10 => Ok(Facility::Authpriv), + 11 => Ok(Facility::Ftp), + 12 => Ok(Facility::Res0), + 13 => Ok(Facility::Res1), + 14 => Ok(Facility::Res2), + 15 => Ok(Facility::Res3), + 16 => Ok(Facility::Local0), + 17 => Ok(Facility::Local1), + 18 => Ok(Facility::Local2), + 19 => Ok(Facility::Local3), + 20 => Ok(Facility::Local4), + 21 => Ok(Facility::Local5), + 22 => Ok(Facility::Local6), + 23 => Ok(Facility::Local7), + _ => todo!(), + } + } +} From 07e01a52766d37caefb9983f6862af1122a5285d Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Mon, 9 Dec 2024 17:25:53 +0700 Subject: [PATCH 06/22] dmesg: cache record regex. --- src/uu/dmesg/src/dmesg.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index fdcf15f..32dfad8 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -10,6 +10,7 @@ use std::{ fs::File, hash::Hash, io::{BufRead, BufReader}, + sync::OnceLock, }; use uucore::{ error::{FromIo, UError, UResult, USimpleError}, @@ -340,7 +341,7 @@ impl RecordIterator { } fn parse_record(&self, record_line: &str) -> Option<Record> { - Self::record_regex() + record_regex() .captures_iter(record_line) .map(|c| c.extract()) .filter_map(|(_, [pri_fac, seq, time, msg])| { @@ -348,8 +349,10 @@ impl RecordIterator { }) .next() } +} - fn record_regex() -> Regex { +fn record_regex() -> &'static Regex { + RECORD_REGEX.get_or_init(|| { let valid_number_pattern = "0|[1-9][0-9]*"; let additional_fields_pattern = ",^[,;]*"; let record_pattern = format!( @@ -357,9 +360,11 @@ impl RecordIterator { valid_number_pattern, additional_fields_pattern ); Regex::new(&record_pattern).expect("invalid regex.") - } + }) } +static RECORD_REGEX: OnceLock<Regex> = OnceLock::new(); + struct Record { priority_facility: u32, _sequence: u64, From 4dbcceede42385b38382bcaaef8f4cf568ea450c Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Wed, 11 Dec 2024 10:32:37 +0700 Subject: [PATCH 07/22] tests/dmesg: add since/until test & fixture. --- tests/by-util/test_dmesg.rs | 11 +++++++++++ tests/fixtures/dmesg/test_since_until.expected | 5 +++++ 2 files changed, 16 insertions(+) create mode 100644 tests/fixtures/dmesg/test_since_until.expected diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index b6cfb73..073c0a5 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -146,3 +146,14 @@ fn test_filter_multiple() { ) }); } + +#[test] +fn test_since_until() { + new_ucmd!() + .arg("--kmsg-file") + .arg("kmsg.input") + .arg("--since=\"2024-11-19 17:47:32\"") + .arg("--until=\"2024-11-19 18:55:52\"") + .succeeds() + .stdout_only_fixture("test_since_until.expected"); +} diff --git a/tests/fixtures/dmesg/test_since_until.expected b/tests/fixtures/dmesg/test_since_until.expected new file mode 100644 index 0000000..2b76c31 --- /dev/null +++ b/tests/fixtures/dmesg/test_since_until.expected @@ -0,0 +1,5 @@ +[80000.000000] LOG_WARNING LOG_AUTH +[81000.000000] LOG_WARNING LOG_AUTHPRIV +[82000.000000] LOG_WARNING LOG_CRON +[83000.000000] LOG_WARNING LOG_DAEMON +[84000.000000] LOG_WARNING LOG_FTP From 8cd9f9d89f7d912d98122a1dfab4792e41d7c938 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Wed, 11 Dec 2024 10:42:07 +0700 Subject: [PATCH 08/22] dmesg: add since/until options. --- src/uu/dmesg/src/dmesg.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 32dfad8..b15afff 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -105,6 +105,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } dmesg.level_filters = Some(level_filters); } + if let Some(_since) = matches.get_one::<String>(options::SINCE) {} + if let Some(_until) = matches.get_one::<String>(options::UNTIL) {} dmesg.print()?; Ok(()) } @@ -151,6 +153,18 @@ pub fn uu_app() -> Command { .help("restrict output to defined levels") .action(ArgAction::Append), ) + .arg( + Arg::new(options::SINCE) + .long("since") + .help("display the lines since the specified time") + .action(ArgAction::Set), + ) + .arg( + Arg::new(options::UNTIL) + .long("until") + .help("display the lines until the specified time") + .action(ArgAction::Set), + ) } mod options { @@ -159,6 +173,8 @@ mod options { pub const TIME_FORMAT: &str = "time-format"; pub const FACILITY: &str = "facility"; pub const LEVEL: &str = "level"; + pub const SINCE: &str = "since"; + pub const UNTIL: &str = "until"; } struct Dmesg<'a> { From 99693490b0c410211090c6b0393f0cffb16c9d9c Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Wed, 11 Dec 2024 11:04:50 +0700 Subject: [PATCH 09/22] tests/dmesg: add since/until invalid time test. --- tests/by-util/test_dmesg.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index 073c0a5..b7f9b69 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -157,3 +157,16 @@ fn test_since_until() { .succeeds() .stdout_only_fixture("test_since_until.expected"); } + +#[test] +fn test_since_until_invalid_time() { + let options = ["--since", "--until"]; + for option in options { + new_ucmd!() + .arg(format!("{option}=definitely-invalid")) + .fails() + .stderr_only(format!( + "dmesg: invalid time value \"definitely-invalid\"\n" + )); + } +} From fb77a356ca65b1d084dd6f94bf88e932c91cd163 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Wed, 11 Dec 2024 11:32:45 +0700 Subject: [PATCH 10/22] dmesg: parse since/until option values. --- Cargo.lock | 28 ++++++++++++++++++++++++++++ src/uu/dmesg/Cargo.toml | 1 + src/uu/dmesg/src/dmesg.rs | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 482217a..4d30315 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "nix" version = "0.29.0" @@ -417,6 +423,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -482,6 +498,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "parse_datetime" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8720474e3dd4af20cea8716703498b9f3b690f318fa9d9d9e2e38eaf44b96d0" +dependencies = [ + "chrono", + "nom", + "regex", +] + [[package]] name = "phf" version = "0.11.2" @@ -1008,6 +1035,7 @@ version = "0.0.1" dependencies = [ "chrono", "clap", + "parse_datetime", "regex", "serde", "serde_json", diff --git a/src/uu/dmesg/Cargo.toml b/src/uu/dmesg/Cargo.toml index c6a8870..e154767 100644 --- a/src/uu/dmesg/Cargo.toml +++ b/src/uu/dmesg/Cargo.toml @@ -17,6 +17,7 @@ regex = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } chrono = "0.4.38" +parse_datetime = "0.6.0" [features] fixed-boot-time = [] diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index b15afff..54b3da4 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -105,8 +105,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } dmesg.level_filters = Some(level_filters); } - if let Some(_since) = matches.get_one::<String>(options::SINCE) {} - if let Some(_until) = matches.get_one::<String>(options::UNTIL) {} + if let Some(since) = matches.get_one::<String>(options::SINCE) { + let since = remove_enclosing_quotes(since); + if let Ok(since) = parse_datetime::parse_datetime(since) { + dmesg.since_filter = Some(since); + } else { + return Err(USimpleError::new( + 1, + format!("invalid time value \"{since}\""), + )); + } + } + if let Some(until) = matches.get_one::<String>(options::UNTIL) { + let until = remove_enclosing_quotes(until); + if let Ok(until) = parse_datetime::parse_datetime(until) { + dmesg.until_filter = Some(until); + } else { + return Err(USimpleError::new( + 1, + format!("invalid time value \"{until}\""), + )); + } + } dmesg.print()?; Ok(()) } @@ -183,6 +203,8 @@ struct Dmesg<'a> { time_format: TimeFormat, facility_filters: Option<HashSet<Facility>>, level_filters: Option<HashSet<Level>>, + since_filter: Option<chrono::DateTime<chrono::FixedOffset>>, + until_filter: Option<chrono::DateTime<chrono::FixedOffset>>, } impl Dmesg<'_> { @@ -193,6 +215,8 @@ impl Dmesg<'_> { time_format: TimeFormat::Raw, facility_filters: None, level_filters: None, + since_filter: None, + until_filter: None, } } @@ -458,3 +482,11 @@ impl TryFrom<u32> for Facility { } } } + +fn remove_enclosing_quotes(value: &str) -> &str { + if value.starts_with('"') && value.ends_with('"') { + &value[1..value.len() - 1] + } else { + value + } +} From c7ff9cd553b6760b0ba31e888010f30ea0afc70c Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Wed, 11 Dec 2024 12:04:15 +0700 Subject: [PATCH 11/22] dmesg: modify is_record_in_set to accept Option. --- src/uu/dmesg/src/dmesg.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 54b3da4..5065325 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -261,17 +261,11 @@ impl Dmesg<'_> { Ok(()) } - fn try_filtered_iter(&self) -> UResult<Box<dyn Iterator<Item = UResult<Record>> + '_>> { - Ok(match (&self.facility_filters, &self.level_filters) { - (None, None) => Box::new(self.try_iter()?), - (None, Some(set)) => Box::new(self.try_iter()?.filter(Self::is_record_in_set(set))), - (Some(set), None) => Box::new(self.try_iter()?.filter(Self::is_record_in_set(set))), - (Some(set_1), Some(set_2)) => Box::new( - self.try_iter()? - .filter(Self::is_record_in_set(set_1)) - .filter(Self::is_record_in_set(set_2)), - ), - }) + fn try_filtered_iter(&self) -> UResult<impl Iterator<Item = UResult<Record>> + '_> { + Ok(self + .try_iter()? + .filter(Self::is_record_in_set(&self.facility_filters)) + .filter(Self::is_record_in_set(&self.level_filters))) } fn try_iter(&self) -> UResult<RecordIterator> { @@ -282,17 +276,17 @@ impl Dmesg<'_> { } fn is_record_in_set<T>( - set: &HashSet<T>, + set: &Option<HashSet<T>>, ) -> impl Fn(&Result<Record, Box<dyn UError>>) -> bool + '_ where T: TryFrom<u32> + Eq + Hash, { - |record: &UResult<Record>| match record { - Ok(record) => match T::try_from(record.priority_facility) { + move |record: &UResult<Record>| match (record, set) { + (Ok(record), Some(set)) => match T::try_from(record.priority_facility) { Ok(t) => set.contains(&t), - Err(_) => true, + Err(_) => false, }, - Err(_) => true, + _ => true, } } } From a35c00b0e48ca33f81789969a11059d759a3ebf4 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Wed, 11 Dec 2024 14:48:45 +0700 Subject: [PATCH 12/22] dmesg: implement since/until filter. --- src/uu/dmesg/src/dmesg.rs | 31 +++++++++++++++++++++++++++++- src/uu/dmesg/src/time_formatter.rs | 6 ++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 5065325..e8917e9 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use chrono::{DateTime, FixedOffset}; use clap::{crate_version, Arg, ArgAction, Command}; use regex::Regex; use std::{ @@ -265,7 +266,9 @@ impl Dmesg<'_> { Ok(self .try_iter()? .filter(Self::is_record_in_set(&self.facility_filters)) - .filter(Self::is_record_in_set(&self.level_filters))) + .filter(Self::is_record_in_set(&self.level_filters)) + .filter(Self::is_record_since(&self.since_filter)) + .filter(Self::is_record_until(&self.until_filter))) } fn try_iter(&self) -> UResult<RecordIterator> { @@ -289,6 +292,32 @@ impl Dmesg<'_> { _ => true, } } + + fn is_record_since( + since: &Option<DateTime<FixedOffset>>, + ) -> impl Fn(&UResult<Record>) -> bool + '_ { + move |record: &UResult<Record>| match (record, since) { + (Ok(record), Some(since)) => { + let time = + time_formatter::datetime_from_microseconds_since_boot(record.timestamp_us); + time >= *since + } + _ => true, + } + } + + fn is_record_until( + until: &Option<DateTime<FixedOffset>>, + ) -> impl Fn(&UResult<Record>) -> bool + '_ { + move |record: &UResult<Record>| match (record, until) { + (Ok(record), Some(until)) => { + let time = + time_formatter::datetime_from_microseconds_since_boot(record.timestamp_us); + time <= *until + } + _ => true, + } + } } enum OutputFormat { diff --git a/src/uu/dmesg/src/time_formatter.rs b/src/uu/dmesg/src/time_formatter.rs index 198431f..24a0be1 100644 --- a/src/uu/dmesg/src/time_formatter.rs +++ b/src/uu/dmesg/src/time_formatter.rs @@ -116,6 +116,12 @@ impl DeltaFormatter { } } +pub fn datetime_from_microseconds_since_boot(microseconds: i64) -> DateTime<FixedOffset> { + boot_time() + .checked_add_signed(TimeDelta::microseconds(microseconds)) + .unwrap() +} + static BOOT_TIME: OnceLock<DateTime<FixedOffset>> = OnceLock::new(); #[cfg(feature = "fixed-boot-time")] From 48c4f780884859e9acafe4e43eaf05ab242044e1 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Wed, 11 Dec 2024 15:48:51 +0700 Subject: [PATCH 13/22] dmesg: set to fixed timezone in test. --- src/uu/dmesg/src/dmesg.rs | 18 ++---------------- src/uu/dmesg/src/time_formatter.rs | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index e8917e9..2ad6427 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -108,25 +108,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } if let Some(since) = matches.get_one::<String>(options::SINCE) { let since = remove_enclosing_quotes(since); - if let Ok(since) = parse_datetime::parse_datetime(since) { - dmesg.since_filter = Some(since); - } else { - return Err(USimpleError::new( - 1, - format!("invalid time value \"{since}\""), - )); - } + dmesg.since_filter = Some(time_formatter::parse_datetime(since)?); } if let Some(until) = matches.get_one::<String>(options::UNTIL) { let until = remove_enclosing_quotes(until); - if let Ok(until) = parse_datetime::parse_datetime(until) { - dmesg.until_filter = Some(until); - } else { - return Err(USimpleError::new( - 1, - format!("invalid time value \"{until}\""), - )); - } + dmesg.until_filter = Some(time_formatter::parse_datetime(until)?); } dmesg.print()?; Ok(()) diff --git a/src/uu/dmesg/src/time_formatter.rs b/src/uu/dmesg/src/time_formatter.rs index 24a0be1..b180d0e 100644 --- a/src/uu/dmesg/src/time_formatter.rs +++ b/src/uu/dmesg/src/time_formatter.rs @@ -7,6 +7,7 @@ use chrono::{DateTime, FixedOffset, TimeDelta}; #[cfg(feature = "fixed-boot-time")] use chrono::{NaiveDate, NaiveTime}; use std::sync::OnceLock; +use uucore::error::{UResult, USimpleError}; pub fn raw(timestamp_us: i64) -> String { let seconds = timestamp_us / 1000000; @@ -116,6 +117,15 @@ impl DeltaFormatter { } } +pub fn parse_datetime(s: &str) -> UResult<DateTime<FixedOffset>> { + #[cfg(feature = "fixed-boot-time")] + set_fixed_timezone(); + match parse_datetime::parse_datetime(s) { + Ok(date_time) => Ok(date_time), + Err(_) => Err(USimpleError::new(1, format!("invalid time value \"{s}\""))), + } +} + pub fn datetime_from_microseconds_since_boot(microseconds: i64) -> DateTime<FixedOffset> { boot_time() .checked_add_signed(TimeDelta::microseconds(microseconds)) @@ -174,3 +184,11 @@ fn boot_time_from_utmpx() -> Option<DateTime<FixedOffset>> { } None } + +#[cfg(feature = "fixed-boot-time")] +static SET_TZ: OnceLock<()> = OnceLock::new(); + +#[cfg(feature = "fixed-boot-time")] +fn set_fixed_timezone() { + *SET_TZ.get_or_init(|| std::env::set_var("TZ", "Asia/Jakarta")) +} From b90751119ddf8f587a515a86ab6621564fdf8834 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Thu, 12 Dec 2024 12:54:58 +0700 Subject: [PATCH 14/22] tests/dmesg: use fixed offset time argument in test. --- src/uu/dmesg/src/time_formatter.rs | 10 ---------- tests/by-util/test_dmesg.rs | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/uu/dmesg/src/time_formatter.rs b/src/uu/dmesg/src/time_formatter.rs index b180d0e..5d93068 100644 --- a/src/uu/dmesg/src/time_formatter.rs +++ b/src/uu/dmesg/src/time_formatter.rs @@ -118,8 +118,6 @@ impl DeltaFormatter { } pub fn parse_datetime(s: &str) -> UResult<DateTime<FixedOffset>> { - #[cfg(feature = "fixed-boot-time")] - set_fixed_timezone(); match parse_datetime::parse_datetime(s) { Ok(date_time) => Ok(date_time), Err(_) => Err(USimpleError::new(1, format!("invalid time value \"{s}\""))), @@ -184,11 +182,3 @@ fn boot_time_from_utmpx() -> Option<DateTime<FixedOffset>> { } None } - -#[cfg(feature = "fixed-boot-time")] -static SET_TZ: OnceLock<()> = OnceLock::new(); - -#[cfg(feature = "fixed-boot-time")] -fn set_fixed_timezone() { - *SET_TZ.get_or_init(|| std::env::set_var("TZ", "Asia/Jakarta")) -} diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index b7f9b69..d8d6836 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -152,8 +152,8 @@ fn test_since_until() { new_ucmd!() .arg("--kmsg-file") .arg("kmsg.input") - .arg("--since=\"2024-11-19 17:47:32\"") - .arg("--until=\"2024-11-19 18:55:52\"") + .arg("--since=\"2024-11-19 17:47:32 +0700\"") + .arg("--until=\"2024-11-19 18:55:52 +0700\"") .succeeds() .stdout_only_fixture("test_since_until.expected"); } From d3b82aa3760128bd2d713f1d3857221baca80c17 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Thu, 12 Dec 2024 12:59:35 +0700 Subject: [PATCH 15/22] tests/dmesg: handle fixture line endings when fixture is checked out on Windows. --- tests/by-util/test_dmesg.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index d8d6836..a75f4a7 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -155,7 +155,8 @@ fn test_since_until() { .arg("--since=\"2024-11-19 17:47:32 +0700\"") .arg("--until=\"2024-11-19 18:55:52 +0700\"") .succeeds() - .stdout_only_fixture("test_since_until.expected"); + .no_stderr() + .stdout_is_templated_fixture("test_since_until.expected", &[("\r\n", "\n")]); } #[test] From d24b1e5c5951ddb9c9167c1586883c3091bfca03 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Thu, 12 Dec 2024 13:05:57 +0700 Subject: [PATCH 16/22] tests/dmesg: add invalid level/facility argument test. --- tests/by-util/test_dmesg.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index a75f4a7..13614c6 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -128,6 +128,24 @@ fn test_filter_levels() { } } +#[test] +fn test_invalid_facility_argument() { + new_ucmd!() + .arg("--facility=definitely-invalid") + .fails() + .code_is(1) + .stderr_only("dmesg: unknown facility 'definitely-invalid'\n"); +} + +#[test] +fn test_invalid_level_argument() { + new_ucmd!() + .arg("--level=definitely-invalid") + .fails() + .code_is(1) + .stderr_only("dmesg: unknown level 'definitely-invalid'\n"); +} + #[test] fn test_filter_multiple() { let mut cmd = new_ucmd!(); From 260cfbfd55c08a55b761acfc8d0a9311496d27d1 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Thu, 12 Dec 2024 13:17:29 +0700 Subject: [PATCH 17/22] tests/dmesg: test since/until argument enclosed with single quotes. --- tests/by-util/test_dmesg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index 13614c6..cc7d930 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -171,7 +171,7 @@ fn test_since_until() { .arg("--kmsg-file") .arg("kmsg.input") .arg("--since=\"2024-11-19 17:47:32 +0700\"") - .arg("--until=\"2024-11-19 18:55:52 +0700\"") + .arg("--until='2024-11-19 18:55:52 +0700'") .succeeds() .no_stderr() .stdout_is_templated_fixture("test_since_until.expected", &[("\r\n", "\n")]); From 9e99aad01b30e0a429fe2a714c6bddf1b33b5d78 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Thu, 12 Dec 2024 13:17:57 +0700 Subject: [PATCH 18/22] dmesg: support single quoted argument for since/until options. --- src/uu/dmesg/src/dmesg.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 2ad6427..8f53085 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -493,7 +493,9 @@ impl TryFrom<u32> for Facility { } fn remove_enclosing_quotes(value: &str) -> &str { - if value.starts_with('"') && value.ends_with('"') { + if (value.starts_with('"') || value.starts_with('\'')) + && (value.ends_with('"') || value.ends_with('\'')) + { &value[1..value.len() - 1] } else { value From 5335f1bc2b590e37bce4e73a6649631abaa36d2a Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sat, 14 Dec 2024 11:22:31 +0700 Subject: [PATCH 19/22] dmesg: simplify expression. Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com> --- src/uu/dmesg/src/time_formatter.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uu/dmesg/src/time_formatter.rs b/src/uu/dmesg/src/time_formatter.rs index 5d93068..f26bb4c 100644 --- a/src/uu/dmesg/src/time_formatter.rs +++ b/src/uu/dmesg/src/time_formatter.rs @@ -118,10 +118,8 @@ impl DeltaFormatter { } pub fn parse_datetime(s: &str) -> UResult<DateTime<FixedOffset>> { - match parse_datetime::parse_datetime(s) { - Ok(date_time) => Ok(date_time), - Err(_) => Err(USimpleError::new(1, format!("invalid time value \"{s}\""))), - } + parse_datetime::parse_datetime(s) + .map_err(|_| USimpleError::new(1, format!("invalid time value \"{s}\""))) } pub fn datetime_from_microseconds_since_boot(microseconds: i64) -> DateTime<FixedOffset> { From d5b2f3c7e157ddbc89fc3873aed0e622bd8ab400 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sat, 14 Dec 2024 22:27:54 +0700 Subject: [PATCH 20/22] tests/dmesg: remove enclosing quotes surrounding since and until option arguments. --- tests/by-util/test_dmesg.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_dmesg.rs b/tests/by-util/test_dmesg.rs index cc7d930..2debb8a 100644 --- a/tests/by-util/test_dmesg.rs +++ b/tests/by-util/test_dmesg.rs @@ -170,8 +170,8 @@ fn test_since_until() { new_ucmd!() .arg("--kmsg-file") .arg("kmsg.input") - .arg("--since=\"2024-11-19 17:47:32 +0700\"") - .arg("--until='2024-11-19 18:55:52 +0700'") + .arg("--since=2024-11-19 17:47:32 +0700") + .arg("--until=2024-11-19 18:55:52 +0700") .succeeds() .no_stderr() .stdout_is_templated_fixture("test_since_until.expected", &[("\r\n", "\n")]); From 5a333b463ecfff48e4cc32398012273e4f09f646 Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sat, 14 Dec 2024 22:28:27 +0700 Subject: [PATCH 21/22] dmesg: remove unnecessary 'remove_enclosing_quotes' function. --- src/uu/dmesg/src/dmesg.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index 8f53085..d09c93e 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -107,11 +107,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { dmesg.level_filters = Some(level_filters); } if let Some(since) = matches.get_one::<String>(options::SINCE) { - let since = remove_enclosing_quotes(since); dmesg.since_filter = Some(time_formatter::parse_datetime(since)?); } if let Some(until) = matches.get_one::<String>(options::UNTIL) { - let until = remove_enclosing_quotes(until); dmesg.until_filter = Some(time_formatter::parse_datetime(until)?); } dmesg.print()?; @@ -491,13 +489,3 @@ impl TryFrom<u32> for Facility { } } } - -fn remove_enclosing_quotes(value: &str) -> &str { - if (value.starts_with('"') || value.starts_with('\'')) - && (value.ends_with('"') || value.ends_with('\'')) - { - &value[1..value.len() - 1] - } else { - value - } -} From e6364af9c683210aa91010b9b37083558358656f Mon Sep 17 00:00:00 2001 From: Fuad Ismail <fuad1502@gmail.com> Date: Sun, 15 Dec 2024 02:33:28 +0700 Subject: [PATCH 22/22] dmesg: parse unknown facility and level as an Unknown. --- src/uu/dmesg/src/dmesg.rs | 89 ++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/src/uu/dmesg/src/dmesg.rs b/src/uu/dmesg/src/dmesg.rs index d09c93e..96c995b 100644 --- a/src/uu/dmesg/src/dmesg.rs +++ b/src/uu/dmesg/src/dmesg.rs @@ -266,13 +266,10 @@ impl Dmesg<'_> { set: &Option<HashSet<T>>, ) -> impl Fn(&Result<Record, Box<dyn UError>>) -> bool + '_ where - T: TryFrom<u32> + Eq + Hash, + T: From<u32> + Eq + Hash, { move |record: &UResult<Record>| match (record, set) { - (Ok(record), Some(set)) => match T::try_from(record.priority_facility) { - Ok(t) => set.contains(&t), - Err(_) => false, - }, + (Ok(record), Some(set)) => set.contains(&T::from(record.priority_facility)), _ => true, } } @@ -344,6 +341,7 @@ enum Facility { Local5, Local6, Local7, + Unknown, } #[derive(Eq, Hash, PartialEq)] @@ -356,6 +354,7 @@ enum Level { Notice, Info, Debug, + Unknown, } struct RecordIterator { @@ -436,56 +435,52 @@ impl Record { } } -impl TryFrom<u32> for Level { - type Error = Box<dyn UError>; - - fn try_from(value: u32) -> UResult<Self> { +impl From<u32> for Level { + fn from(value: u32) -> Self { let priority = value & 0b111; match priority { - 0 => Ok(Level::Emerg), - 1 => Ok(Level::Alert), - 2 => Ok(Level::Crit), - 3 => Ok(Level::Err), - 4 => Ok(Level::Warn), - 5 => Ok(Level::Notice), - 6 => Ok(Level::Info), - 7 => Ok(Level::Debug), - _ => todo!(), + 0 => Level::Emerg, + 1 => Level::Alert, + 2 => Level::Crit, + 3 => Level::Err, + 4 => Level::Warn, + 5 => Level::Notice, + 6 => Level::Info, + 7 => Level::Debug, + _ => Level::Unknown, } } } -impl TryFrom<u32> for Facility { - type Error = Box<dyn UError>; - - fn try_from(value: u32) -> Result<Self, Self::Error> { +impl From<u32> for Facility { + fn from(value: u32) -> Self { let facility = (value >> 3) as u8; match facility { - 0 => Ok(Facility::Kern), - 1 => Ok(Facility::User), - 2 => Ok(Facility::Mail), - 3 => Ok(Facility::Daemon), - 4 => Ok(Facility::Auth), - 5 => Ok(Facility::Syslog), - 6 => Ok(Facility::Lpr), - 7 => Ok(Facility::News), - 8 => Ok(Facility::Uucp), - 9 => Ok(Facility::Cron), - 10 => Ok(Facility::Authpriv), - 11 => Ok(Facility::Ftp), - 12 => Ok(Facility::Res0), - 13 => Ok(Facility::Res1), - 14 => Ok(Facility::Res2), - 15 => Ok(Facility::Res3), - 16 => Ok(Facility::Local0), - 17 => Ok(Facility::Local1), - 18 => Ok(Facility::Local2), - 19 => Ok(Facility::Local3), - 20 => Ok(Facility::Local4), - 21 => Ok(Facility::Local5), - 22 => Ok(Facility::Local6), - 23 => Ok(Facility::Local7), - _ => todo!(), + 0 => Facility::Kern, + 1 => Facility::User, + 2 => Facility::Mail, + 3 => Facility::Daemon, + 4 => Facility::Auth, + 5 => Facility::Syslog, + 6 => Facility::Lpr, + 7 => Facility::News, + 8 => Facility::Uucp, + 9 => Facility::Cron, + 10 => Facility::Authpriv, + 11 => Facility::Ftp, + 12 => Facility::Res0, + 13 => Facility::Res1, + 14 => Facility::Res2, + 15 => Facility::Res3, + 16 => Facility::Local0, + 17 => Facility::Local1, + 18 => Facility::Local2, + 19 => Facility::Local3, + 20 => Facility::Local4, + 21 => Facility::Local5, + 22 => Facility::Local6, + 23 => Facility::Local7, + _ => Facility::Unknown, } } }