rmesg/
lib.rs

1mod common;
2
3pub mod entry;
4pub mod error;
5/// KLog Implementation (makes klogctl aka syslog system call through libc)
6pub mod klogctl;
7/// KMsg Implementation (reads from the /dev/kmsg file)
8pub mod kmsgfile;
9
10#[cfg(feature = "sync")]
11use std::iter::Iterator;
12
13#[cfg(feature = "async")]
14use core::pin::Pin;
15#[cfg(feature = "async")]
16use futures::stream::Stream;
17#[cfg(feature = "async")]
18use futures::task::{Context, Poll};
19#[cfg(feature = "async")]
20use pin_project::pin_project;
21
22#[derive(Clone, Copy, Debug)]
23pub enum Backend {
24    Default,
25    KLogCtl,
26    DevKMsg,
27}
28
29#[cfg(feature = "sync")]
30pub enum EntriesIterator {
31    KLogCtl(klogctl::KLogEntries),
32    DevKMsg(kmsgfile::KMsgEntriesIter),
33}
34#[cfg(feature = "sync")]
35impl Iterator for EntriesIterator {
36    type Item = Result<entry::Entry, error::RMesgError>;
37    fn next(&mut self) -> Option<Self::Item> {
38        match self {
39            Self::KLogCtl(k) => k.next(),
40            Self::DevKMsg(d) => d.next(),
41        }
42    }
43}
44
45#[pin_project(project = EntriesStreamPinnedProjection)]
46#[cfg(feature = "async")]
47pub enum EntriesStream {
48    KLogCtl(#[pin] klogctl::KLogEntries),
49    DevKMsg(#[pin] kmsgfile::KMsgEntriesStream),
50}
51#[cfg(feature = "async")]
52impl Stream for EntriesStream {
53    type Item = Result<entry::Entry, error::RMesgError>;
54    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
55        match self.project() {
56            EntriesStreamPinnedProjection::KLogCtl(k) => k.poll_next(cx),
57            EntriesStreamPinnedProjection::DevKMsg(d) => d.poll_next(cx),
58        }
59    }
60}
61
62pub fn log_entries(b: Backend, clear: bool) -> Result<Vec<entry::Entry>, error::RMesgError> {
63    match b {
64        Backend::Default => match kmsgfile::kmsg(None) {
65            Ok(e) => Ok(e),
66            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
67                eprintln!(
68                    "Falling back from device file to klogctl syscall due to error: {}",
69                    s
70                );
71                klogctl::klog(clear)
72            }
73            Err(e) => Err(e),
74        },
75        Backend::KLogCtl => klogctl::klog(clear),
76        Backend::DevKMsg => kmsgfile::kmsg(None),
77    }
78}
79
80pub fn logs_raw(b: Backend, clear: bool) -> Result<String, error::RMesgError> {
81    match b {
82        Backend::Default => match kmsgfile::kmsg_raw(None) {
83            Ok(e) => Ok(e),
84            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
85                eprintln!(
86                    "Falling back from device file to klogctl syscall due to error: {}",
87                    s
88                );
89                klogctl::klog_raw(clear)
90            }
91            Err(e) => Err(e),
92        },
93        Backend::KLogCtl => klogctl::klog_raw(clear),
94        Backend::DevKMsg => kmsgfile::kmsg_raw(None),
95    }
96}
97
98#[cfg(feature = "sync")]
99pub fn logs_iter(b: Backend, clear: bool, raw: bool) -> Result<EntriesIterator, error::RMesgError> {
100    match b {
101        Backend::Default => match kmsgfile::KMsgEntriesIter::with_options(None, raw) {
102            Ok(e) => Ok(EntriesIterator::DevKMsg(e)),
103            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
104                eprintln!(
105                    "Falling back from device file to klogctl syscall due to error: {}",
106                    s
107                );
108                Ok(EntriesIterator::KLogCtl(
109                    klog_entries_only_if_timestamp_enabled(clear)?,
110                ))
111            }
112            Err(e) => Err(e),
113        },
114        Backend::KLogCtl => Ok(EntriesIterator::KLogCtl(
115            klog_entries_only_if_timestamp_enabled(clear)?,
116        )),
117        Backend::DevKMsg => Ok(EntriesIterator::DevKMsg(
118            kmsgfile::KMsgEntriesIter::with_options(None, raw)?,
119        )),
120    }
121}
122
123#[cfg(feature = "async")]
124pub async fn logs_stream(
125    b: Backend,
126    clear: bool,
127    raw: bool,
128) -> Result<EntriesStream, error::RMesgError> {
129    match b {
130        Backend::Default => match kmsgfile::KMsgEntriesStream::with_options(None, raw).await {
131            Ok(e) => Ok(EntriesStream::DevKMsg(e)),
132            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
133                eprintln!(
134                    "Falling back from device file to klogctl syscall due to error: {}",
135                    s
136                );
137                Ok(EntriesStream::KLogCtl(
138                    klog_entries_only_if_timestamp_enabled(clear)?,
139                ))
140            }
141            Err(e) => Err(e),
142        },
143        Backend::KLogCtl => Ok(EntriesStream::KLogCtl(
144            klog_entries_only_if_timestamp_enabled(clear)?,
145        )),
146        Backend::DevKMsg => Ok(EntriesStream::DevKMsg(
147            kmsgfile::KMsgEntriesStream::with_options(None, raw).await?,
148        )),
149    }
150}
151
152fn klog_entries_only_if_timestamp_enabled(
153    clear: bool,
154) -> Result<klogctl::KLogEntries, error::RMesgError> {
155    let log_timestamps_enabled = klogctl::klog_timestamps_enabled()?;
156
157    // ensure timestamps in logs
158    if !log_timestamps_enabled {
159        eprintln!("WARNING: Timestamps are disabled but tailing/following logs (as you've requested) requires them.");
160        eprintln!("Aboring program.");
161        eprintln!("You can enable timestamps by running the following: ");
162        eprintln!("  echo Y > /sys/module/printk/parameters/time");
163        return Err(error::RMesgError::KLogTimestampsDisabled);
164    }
165
166    klogctl::KLogEntries::with_options(clear, klogctl::SUGGESTED_POLL_INTERVAL)
167}
168
169/**********************************************************************************/
170// Tests! Tests! Tests!
171
172#[cfg(all(test, target_os = "linux"))]
173mod test {
174    use super::*;
175    #[cfg(feature = "async")]
176    use tokio_stream::StreamExt;
177
178    #[test]
179    fn test_log_entries() {
180        let entries = log_entries(Backend::Default, false);
181        assert!(entries.is_ok(), "Response from kmsg not Ok");
182        assert!(!entries.unwrap().is_empty(), "Should have non-empty logs");
183    }
184
185    #[cfg(feature = "sync")]
186    #[test]
187    fn test_iterator() {
188        // uncomment below if you want to be extra-sure
189        //let enable_timestamp_result = kernel_log_timestamps_enable(true);
190        //assert!(enable_timestamp_result.is_ok());
191
192        // Don't clear the buffer. Poll every second.
193        let iterator_result = logs_iter(Backend::Default, false, false);
194        assert!(iterator_result.is_ok());
195
196        let iterator = iterator_result.unwrap();
197
198        // Read 10 lines and quit
199        for (count, entry) in iterator.enumerate() {
200            assert!(entry.is_ok());
201            if count > 10 {
202                break;
203            }
204        }
205    }
206
207    #[cfg(feature = "async")]
208    #[tokio::test]
209    async fn test_stream() {
210        // uncomment below if you want to be extra-sure
211        //let enable_timestamp_result = kernel_log_timestamps_enable(true);
212        //assert!(enable_timestamp_result.is_ok());
213
214        // Don't clear the buffer. Poll every second.
215        let stream_result = logs_stream(Backend::Default, false, false).await;
216        assert!(stream_result.is_ok());
217
218        let mut stream = stream_result.unwrap();
219
220        // Read 10 lines and quit
221        let mut count: u32 = 0;
222        while let Some(entry) = stream.next().await {
223            assert!(entry.is_ok());
224            count += 1;
225            if count > 10 {
226                break;
227            }
228        }
229    }
230}