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,
         }
     }
 }