use crate::common;
use crate::entry::{Entry, EntryParsingError};
use crate::error::RMesgError;
use errno::errno;
use lazy_static::lazy_static;
use regex::Regex;
use std::convert::TryFrom;
use std::fs;
use std::time::{Duration, SystemTime};
use strum_macros::Display;
pub const SUGGESTED_POLL_INTERVAL: std::time::Duration = Duration::from_secs(10);
#[cfg(feature = "async")]
use core::future::Future;
#[cfg(feature = "async")]
use core::pin::Pin;
#[cfg(feature = "async")]
use futures::stream::Stream;
#[cfg(feature = "async")]
use futures::task::{Context, Poll};
#[cfg(feature = "async")]
use tokio::time as tokiotime;
#[cfg(feature = "sync")]
use std::iter::Iterator;
#[cfg(feature = "sync")]
use std::thread;
#[cfg(target_os = "linux")]
extern "C" {
fn klogctl(syslog_type: libc::c_int, buf: *mut libc::c_char, len: libc::c_int) -> libc::c_int;
}
#[cfg(not(target_os = "linux"))]
unsafe fn klogctl(
_syslog_type: libc::c_int,
_buf: *mut libc::c_char,
_len: libc::c_int,
) -> libc::c_int {
-1
}
#[derive(Debug, Display, Clone)]
pub enum KLogType {
SyslogActionClose,
SyslogActionOpen,
SyslogActionRead,
SyslogActionReadAll,
SyslogActionReadClear,
SyslogActionClear,
SyslogActionConsoleOff,
SyslogActionConsoleOn,
SyslogActionConsoleLevel,
SyslogActionSizeUnread,
SyslogActionSizeBuffer,
}
pub type SignedInt = libc::c_int;
pub const SYS_MODULE_PRINTK_PARAMETERS_TIME: &str = "/sys/module/printk/parameters/time";
pub struct KLogEntries {
clear: bool,
entries: Vec<Entry>,
last_timestamp: Option<Duration>,
poll_interval: Duration,
sleep_interval: Duration,
last_poll: SystemTime,
#[cfg(feature = "async")]
sleep_future: Option<Pin<Box<tokiotime::Sleep>>>,
}
impl KLogEntries {
pub fn with_options(clear: bool, poll_interval: Duration) -> Result<KLogEntries, RMesgError> {
let sleep_interval = match poll_interval.checked_add(Duration::from_millis(200)) {
Some(si) => si,
None => return Err(RMesgError::UnableToAddDurationToSystemTime),
};
let last_poll = match SystemTime::now().checked_sub(sleep_interval) {
Some(lp) => lp,
None => return Err(RMesgError::UnableToAddDurationToSystemTime),
};
Ok(KLogEntries {
entries: Vec::new(),
poll_interval,
sleep_interval,
last_poll,
clear,
last_timestamp: None,
#[cfg(feature = "async")]
sleep_future: None,
})
}
fn poll(&mut self) -> Result<usize, RMesgError> {
self.last_poll = SystemTime::now();
let mut entries = klog(self.clear)?;
let mut entriesadded: usize = 0;
match self.last_timestamp {
None => {
entriesadded += entries.len();
self.entries.append(&mut entries);
}
Some(last_timestamp) => {
while !entries.is_empty() {
let entry = entries.remove(0);
let skip = match entry.timestamp_from_system_start {
Some(timestamp) => timestamp <= last_timestamp,
None => true,
};
if !skip {
self.entries.push(entry);
entriesadded += 1;
}
}
}
};
if let Some(entry) = self.entries.last() {
if entry.timestamp_from_system_start.is_some() {
self.last_timestamp = entry.timestamp_from_system_start;
}
}
Ok(entriesadded)
}
}
#[cfg(feature = "sync")]
impl Iterator for KLogEntries {
type Item = Result<Entry, RMesgError>;
fn next(&mut self) -> Option<Self::Item> {
while self.entries.is_empty() {
let elapsed = match self.last_poll.elapsed() {
Ok(duration) => duration,
Err(e) => return Some(Err(RMesgError::UnableToObtainElapsedTime(e))),
};
if elapsed >= self.poll_interval {
if let Err(e) = self.poll() {
return Some(Err(e));
}
} else {
thread::sleep(self.sleep_interval);
}
}
Some(Ok(self.entries.remove(0)))
}
}
#[cfg(feature = "async")]
impl Stream for KLogEntries {
type Item = Result<Entry, RMesgError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if let Some(mut sf) = self.sleep_future.take() {
match Future::poll(sf.as_mut(), cx) {
Poll::Pending => {
self.sleep_future = Some(sf);
return Poll::Pending;
}
Poll::Ready(()) => {}
}
}
while self.entries.is_empty() {
let elapsed = match self.last_poll.elapsed() {
Ok(duration) => duration,
Err(e) => return Poll::Ready(Some(Err(RMesgError::UnableToObtainElapsedTime(e)))),
};
if elapsed >= self.poll_interval {
if let Err(e) = self.poll() {
return Poll::Ready(Some(Err(e)));
}
} else {
let sf = tokiotime::sleep(self.sleep_interval);
let mut pinned_sf = Box::pin(sf);
if Future::poll(pinned_sf.as_mut(), cx).is_pending() {
self.sleep_future = Some(pinned_sf);
return Poll::Pending;
}
}
}
Poll::Ready(Some(Ok(self.entries.remove(0))))
}
}
pub fn klog_raw(clear: bool) -> Result<String, RMesgError> {
let mut dummy_buffer: Vec<u8> = vec![0; 0];
let kernel_buffer_size =
safely_wrapped_klogctl(KLogType::SyslogActionSizeBuffer, &mut dummy_buffer)?;
let klogtype = match clear {
true => KLogType::SyslogActionReadClear,
false => KLogType::SyslogActionReadAll,
};
let mut real_buffer: Vec<u8> = vec![0; kernel_buffer_size];
let bytes_read = safely_wrapped_klogctl(klogtype, &mut real_buffer)?;
real_buffer.resize(bytes_read, 0);
let utf8_str = String::from_utf8(real_buffer)?;
Ok(utf8_str)
}
pub fn klog(clear: bool) -> Result<Vec<Entry>, RMesgError> {
let all_lines = klog_raw(clear)?;
let lines = all_lines.as_str().lines();
let mut entries = Vec::<Entry>::new();
for line in lines {
entries.push(entry_from_line(line)?)
}
Ok(entries)
}
pub fn klog_timestamps_enabled() -> Result<bool, RMesgError> {
Ok(fs::read_to_string(SYS_MODULE_PRINTK_PARAMETERS_TIME)?
.trim()
.to_uppercase()
== "Y")
}
pub fn klog_timestamps_enable(desired: bool) -> Result<(), RMesgError> {
Ok(fs::write(
SYS_MODULE_PRINTK_PARAMETERS_TIME,
match desired {
true => "Y\n",
false => "N\n",
},
)?)
}
pub fn entry_from_line(line: &str) -> Result<Entry, EntryParsingError> {
lazy_static! {
static ref RE_ENTRY_WITH_TIMESTAMP: Regex = Regex::new(
r"(?x)^
[[:space:]]*<(?P<faclevstr>[[:digit:]]*)>
[[:space:]]*([\[][[:space:]]*(?P<timestampstr>[[:digit:]]*\.[[:digit:]]*)[\]])?
(?P<message>.*)$"
)
.unwrap();
}
if line.trim() == "" {
return Err(EntryParsingError::EmptyLine);
}
let (facility, level, timestamp_from_system_start, message) =
if let Some(klogparts) = RE_ENTRY_WITH_TIMESTAMP.captures(&line) {
let (facility, level) = match klogparts.name("faclevstr") {
Some(faclevstr) => common::parse_favlecstr(faclevstr.as_str(), line)?,
None => (None, None),
};
let timestamp_from_system_start = match klogparts.name("timestampstr") {
Some(timestampstr) => common::parse_timestamp_secs(timestampstr.as_str(), line)?,
None => None,
};
let message = klogparts["message"].to_owned();
(facility, level, timestamp_from_system_start, message)
} else {
(None, None, None, line.to_owned())
};
Ok(Entry {
facility,
level,
sequence_num: None,
timestamp_from_system_start,
message,
})
}
pub fn safely_wrapped_klogctl(klogtype: KLogType, buf_u8: &mut [u8]) -> Result<usize, RMesgError> {
let klt = klogtype.clone() as libc::c_int;
let buf_i8 = buf_u8.as_mut_ptr() as *mut i8;
let buflen = match libc::c_int::try_from(buf_u8.len()) {
Ok(i) => i,
Err(e) => {
return Err(RMesgError::IntegerOutOfBound(format!(
"Error converting buffer length for klogctl from <usize>::({}) into <c_int>: {:?}",
buf_u8.len(),
e
)))
}
};
let response_cint: libc::c_int = unsafe { klogctl(klt, buf_i8, buflen) };
if response_cint < 0 {
let err = errno();
return Err(RMesgError::InternalError(format!(
"Request ({}) to klogctl failed. errno={}",
klogtype, err
)));
}
let response = match usize::try_from(response_cint) {
Ok(i) => i,
Err(e) => {
return Err(RMesgError::IntegerOutOfBound(format!(
"Error converting response from klogctl from <c_int>::({}) into <usize>: {:?}",
response_cint, e
)))
}
};
Ok(response)
}
#[cfg(all(test, target_os = "linux"))]
mod test {
use super::*;
#[test]
fn get_kernel_buffer_size() {
let mut dummy_buffer: Vec<u8> = vec![0; 0];
let response = safely_wrapped_klogctl(KLogType::SyslogActionSizeBuffer, &mut dummy_buffer);
assert!(response.is_ok(), "Response from klogctl not Ok");
assert!(
response.unwrap() > 0,
"Buffer size should be greater than zero."
);
}
#[test]
fn test_klog() {
let entries = klog(false);
assert!(entries.is_ok(), "Response from klog not Ok");
assert!(!entries.unwrap().is_empty(), "Should have non-empty logs");
}
#[cfg(feature = "sync")]
#[test]
fn test_iterator() {
let iterator_result = KLogEntries::with_options(false, SUGGESTED_POLL_INTERVAL);
assert!(iterator_result.is_ok());
let iterator = iterator_result.unwrap();
for (count, entry) in iterator.enumerate() {
assert!(entry.is_ok());
if count > 10 {
break;
}
}
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_stream() {
let stream_result = KLogEntries::with_options(false, SUGGESTED_POLL_INTERVAL);
assert!(stream_result.is_ok());
let mut stream = stream_result.unwrap();
let mut count: u32 = 0;
while let Some(entry) = tokio_stream::StreamExt::next(&mut stream).await {
assert!(entry.is_ok());
count += 1;
if count > 10 {
break;
}
}
}
#[test]
fn test_parse_serialize() {
let line1 = "<6>a.out[4054]: segfault at 7ffd5503d358 ip 00007ffd5503d358 sp 00007ffd5503d258 error 15";
let e1r = entry_from_line(line1);
assert!(e1r.is_ok());
let line1again = e1r.unwrap().to_klog_str();
assert_eq!(line1, line1again);
let line2 = "<7>[ 233434.343533] a.out[4054]: segfault at 7ffd5503d358 ip 00007ffd5503d358 sp 00007ffd5503d258 error 15";
let e2r = entry_from_line(line2);
assert!(e2r.is_ok());
let line2again = e2r.unwrap().to_klog_str();
assert_eq!(line2, line2again);
let line3 = "233434.343533] a.out[4054]: segfault at 7ffd5503d358 ip 00007ffd5503d358 sp 00007ffd5503d258 error 15";
let e3r = entry_from_line(line3);
assert!(e3r.is_ok());
let line3again = e3r.unwrap().to_klog_str();
assert_eq!(line3, line3again);
}
}