1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate enum_display_derive;
#[macro_use]
extern crate num_derive;

mod common;

pub mod entry;
pub mod error;

/// KLog Implementation (makes klogctl aka syslog system call through libc)
pub mod klogctl;

/// KMsg Implementation (reads from the /dev/kmsg file)
pub mod kmsgfile;

#[cfg(feature = "sync")]
use std::iter::Iterator;

#[cfg(feature = "async")]
use core::pin::Pin;
#[cfg(feature = "async")]
use futures::stream::Stream;

#[derive(Clone, Copy, Debug)]
pub enum Backend {
    Default,
    KLogCtl,
    DevKMsg,
}

#[cfg(feature = "sync")]
pub type EntriesIterator = Box<dyn Iterator<Item = Result<entry::Entry, error::RMesgError>>>;

#[cfg(feature = "async")]
pub type EntriesStream = Pin<Box<dyn Stream<Item = Result<entry::Entry, error::RMesgError>>>>;

pub fn log_entries(b: Backend, clear: bool) -> Result<Vec<entry::Entry>, error::RMesgError> {
    match b {
        Backend::Default => match kmsgfile::kmsg(None) {
            Ok(e) => Ok(e),
            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
                eprintln!(
                    "Falling back from device file to klogctl syscall due to error: {}",
                    s
                );
                klogctl::klog(clear)
            }
            Err(e) => Err(e),
        },
        Backend::KLogCtl => klogctl::klog(clear),
        Backend::DevKMsg => kmsgfile::kmsg(None),
    }
}

pub fn logs_raw(b: Backend, clear: bool) -> Result<String, error::RMesgError> {
    match b {
        Backend::Default => match kmsgfile::kmsg_raw(None) {
            Ok(e) => Ok(e),
            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
                eprintln!(
                    "Falling back from device file to klogctl syscall due to error: {}",
                    s
                );
                klogctl::klog_raw(clear)
            }
            Err(e) => Err(e),
        },
        Backend::KLogCtl => klogctl::klog_raw(clear),
        Backend::DevKMsg => kmsgfile::kmsg_raw(None),
    }
}

#[cfg(feature = "sync")]
pub fn logs_iter(b: Backend, clear: bool, raw: bool) -> Result<EntriesIterator, error::RMesgError> {
    match b {
        Backend::Default => match kmsgfile::KMsgEntriesIter::with_options(None, raw) {
            Ok(e) => Ok(Box::new(e)),
            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
                eprintln!(
                    "Falling back from device file to klogctl syscall due to error: {}",
                    s
                );
                Ok(Box::new(klog_entries_only_if_timestamp_enabled(clear)?))
            }
            Err(e) => Err(e),
        },
        Backend::KLogCtl => Ok(Box::new(klog_entries_only_if_timestamp_enabled(clear)?)),
        Backend::DevKMsg => Ok(Box::new(kmsgfile::KMsgEntriesIter::with_options(
            None, raw,
        )?)),
    }
}

#[cfg(feature = "async")]
pub async fn logs_stream(
    b: Backend,
    clear: bool,
    raw: bool,
) -> Result<EntriesStream, error::RMesgError> {
    match b {
        Backend::Default => match kmsgfile::KMsgEntriesStream::with_options(None, raw).await {
            Ok(e) => Ok(Box::pin(e)),
            Err(error::RMesgError::DevKMsgFileOpenError(s)) => {
                eprintln!(
                    "Falling back from device file to klogctl syscall due to error: {}",
                    s
                );
                Ok(Box::pin(klog_entries_only_if_timestamp_enabled(clear)?))
            }
            Err(e) => Err(e),
        },
        Backend::KLogCtl => Ok(Box::pin(klog_entries_only_if_timestamp_enabled(clear)?)),
        Backend::DevKMsg => Ok(Box::pin(
            kmsgfile::KMsgEntriesStream::with_options(None, raw).await?,
        )),
    }
}

fn klog_entries_only_if_timestamp_enabled(
    clear: bool,
) -> Result<klogctl::KLogEntries, error::RMesgError> {
    let log_timestamps_enabled = klogctl::klog_timestamps_enabled()?;

    // ensure timestamps in logs
    if !log_timestamps_enabled {
        eprintln!("WARNING: Timestamps are disabled but tailing/following logs (as you've requested) requires them.");
        eprintln!("Aboring program.");
        eprintln!("You can enable timestamps by running the following: ");
        eprintln!("  echo Y > /sys/module/printk/parameters/time");
        return Err(error::RMesgError::KLogTimestampsDisabled);
    }

    klogctl::KLogEntries::with_options(clear, klogctl::SUGGESTED_POLL_INTERVAL)
}