use libc::{c_char, c_int, size_t};
use log::{self, Log, Record, Level, SetLoggerError};
use std::{io, ptr, result, fmt};
use std::collections::BTreeMap;
use std::ffi::CString;
use std::io::ErrorKind::InvalidData;
use std::os::raw::c_void;
use std::u64;
use ffi::array_to_iovecs;
use ffi::id128::sd_id128_t;
use ffi::journal as ffi;
use id128::Id128;
use super::{free_cstring, Result};
use std::time;
pub fn send(args: &[&str]) -> c_int {
let iovecs = array_to_iovecs(args);
unsafe { ffi::sd_journal_sendv(iovecs.as_ptr(), iovecs.len() as c_int) }
}
pub fn print(lvl: u32, s: &str) -> c_int {
send(&[&format!("PRIORITY={}", lvl), &format!("MESSAGE={}", s)])
}
enum SyslogLevel {
Err = 3,
Warning = 4,
Info = 6,
Debug = 7,
}
pub fn log(level: usize, file: &str, line: u32, module_path: &str, args: &fmt::Arguments) {
send(&[&format!("PRIORITY={}", level),
&format!("MESSAGE={}", args),
&format!("CODE_LINE={}", line),
&format!("CODE_FILE={}", file),
&format!("CODE_FUNCTION={}", module_path)]);
}
pub fn log_record(record: &Record) {
let lvl = match record.level() {
Level::Error => SyslogLevel::Err,
Level::Warn => SyslogLevel::Warning,
Level::Info => SyslogLevel::Info,
Level::Debug |
Level::Trace => SyslogLevel::Debug,
} as usize;
let mut keys = vec![
format!("PRIORITY={}", lvl),
format!("MESSAGE={}", record.args()),
format!("TARGET={}", record.target()),
];
record.line().map(|line| keys.push(format!("CODE_LINE={}", line)));
record.file().map(|file| keys.push(format!("CODE_FILE={}", file)));
record.module_path().map(|module_path| keys.push(format!("CODE_FUNCTION={}", module_path)));
let str_keys = keys.iter().map(AsRef::as_ref).collect::<Vec<_>>();
send(&str_keys);
}
pub struct JournalLog;
impl Log for JournalLog {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
log_record(record);
}
fn flush(&self) {
}
}
static LOGGER: JournalLog = JournalLog;
impl JournalLog {
pub fn init() -> result::Result<(), SetLoggerError> {
log::set_logger(&LOGGER)
}
}
fn duration_from_usec(usec: u64) -> time::Duration {
let secs = usec / 1_000_000;
let sub_usec = (usec % 1_000_000) as u32;
let sub_nsec = sub_usec * 1000;
time::Duration::new(secs, sub_nsec)
}
fn usec_from_duration(duration: time::Duration) -> u64 {
let sub_usecs = (duration.subsec_nanos() / 1000) as u64;
duration.as_secs() * 1_000_000 + sub_usecs
}
fn system_time_from_realtime_usec(usec: u64) -> time::SystemTime {
let d = duration_from_usec(usec);
time::UNIX_EPOCH + d
}
pub type JournalRecord = BTreeMap<String, String>;
pub struct Journal {
j: *mut ffi::sd_journal,
}
#[derive(Clone, Debug)]
pub enum JournalFiles {
System,
CurrentUser,
All,
}
pub enum JournalSeek {
Head,
Current,
Tail,
ClockMonotonic {
boot_id: Id128,
usec: u64,
},
ClockRealtime {
usec: u64,
},
Cursor {
cursor: String,
},
}
#[derive(Clone, Debug)]
pub enum JournalWaitResult {
Nop,
Append,
Invalidate
}
impl Journal {
pub fn open(files: JournalFiles, runtime_only: bool, local_only: bool) -> Result<Journal> {
let mut flags: c_int = 0;
if runtime_only {
flags |= ffi::SD_JOURNAL_RUNTIME_ONLY;
}
if local_only {
flags |= ffi::SD_JOURNAL_LOCAL_ONLY;
}
flags |= match files {
JournalFiles::System => ffi::SD_JOURNAL_SYSTEM,
JournalFiles::CurrentUser => ffi::SD_JOURNAL_CURRENT_USER,
JournalFiles::All => 0,
};
let mut journal = Journal { j: ptr::null_mut() };
sd_try!(ffi::sd_journal_open(&mut journal.j, flags));
sd_try!(ffi::sd_journal_seek_head(journal.j));
Ok(journal)
}
fn get_record(&mut self) -> Result<Option<JournalRecord>> {
unsafe { ffi::sd_journal_restart_data(self.j) }
let mut ret: JournalRecord = BTreeMap::new();
let mut sz: size_t = 0;
let data: *mut u8 = ptr::null_mut();
while sd_try!(ffi::sd_journal_enumerate_data(self.j, &data, &mut sz)) > 0 {
unsafe {
let b = ::std::slice::from_raw_parts_mut(data, sz as usize);
let field = String::from_utf8_lossy(b);
let mut name_value = field.splitn(2, '=');
let name = name_value.next().unwrap();
let value = name_value.next().unwrap();
ret.insert(From::from(name), From::from(value));
}
}
Ok(Some(ret))
}
pub fn next_record(&mut self) -> Result<Option<JournalRecord>> {
if sd_try!(ffi::sd_journal_next(self.j)) == 0 {
return Ok(None);
}
self.get_record()
}
pub fn previous_record(&mut self) -> Result<Option<JournalRecord>> {
if sd_try!(ffi::sd_journal_previous(self.j)) == 0 {
return Ok(None);
}
self.get_record()
}
fn wait(&mut self, wait_time: Option<time::Duration>) -> Result<JournalWaitResult> {
let time = wait_time.map(usec_from_duration).unwrap_or(::std::u64::MAX);
match sd_try!(ffi::sd_journal_wait(self.j, time)) {
ffi::SD_JOURNAL_NOP => Ok(JournalWaitResult::Nop),
ffi::SD_JOURNAL_APPEND => Ok(JournalWaitResult::Append),
ffi::SD_JOURNAL_INVALIDATE => Ok(JournalWaitResult::Invalidate),
_ => Err(io::Error::new(InvalidData, "Failed to wait for changes"))
}
}
pub fn await_next_record(&mut self, wait_time: Option<time::Duration>) -> Result<Option<JournalRecord>> {
match self.wait(wait_time)? {
JournalWaitResult::Nop => Ok(None),
JournalWaitResult::Append => self.next_record(),
JournalWaitResult::Invalidate => self.next_record()
}
}
pub fn watch_all_elements<F>(&mut self, mut f: F) -> Result<()>
where F: FnMut(JournalRecord) -> Result<()> {
loop {
let candidate = self.next_record()?;
let rec = match candidate {
Some(rec) => rec,
None => { loop {
if let Some(r) = self.await_next_record(None)? {
break r;
}
}}
};
f(rec)?
}
}
pub fn seek(&mut self, seek: JournalSeek) -> Result<String> {
let mut tail = false;
match seek {
JournalSeek::Head => sd_try!(ffi::sd_journal_seek_head(self.j)),
JournalSeek::Current => 0,
JournalSeek::Tail => {
tail = true;
sd_try!(ffi::sd_journal_seek_tail(self.j))
}
JournalSeek::ClockMonotonic { boot_id, usec } => {
sd_try!(ffi::sd_journal_seek_monotonic_usec(self.j,
sd_id128_t {
bytes: *boot_id.as_bytes(),
},
usec))
}
JournalSeek::ClockRealtime { usec } => {
sd_try!(ffi::sd_journal_seek_realtime_usec(self.j, usec))
}
JournalSeek::Cursor { cursor } => {
let c = try!(CString::new(cursor));
sd_try!(ffi::sd_journal_seek_cursor(self.j, c.as_ptr()))
}
};
let c: *mut c_char = ptr::null_mut();
if unsafe { ffi::sd_journal_get_cursor(self.j, &c) != 0 } {
if tail {
sd_try!(ffi::sd_journal_previous(self.j));
} else {
sd_try!(ffi::sd_journal_next(self.j));
}
sd_try!(ffi::sd_journal_get_cursor(self.j, &c));
}
let cs = free_cstring(c).unwrap();
Ok(cs)
}
pub fn cursor(&self) -> Result<String> {
let mut c_cursor: *mut c_char = ptr::null_mut();
sd_try!(ffi::sd_journal_get_cursor(self.j, &mut c_cursor));
let cursor = free_cstring(c_cursor).unwrap();
Ok(cursor)
}
pub fn timestamp(&self) -> Result<time::SystemTime> {
let mut timestamp_us: u64 = 0;
sd_try!(ffi::sd_journal_get_realtime_usec(self.j, &mut timestamp_us));
Ok(system_time_from_realtime_usec(timestamp_us))
}
pub fn monotonic_timestamp(&self) -> Result<(u64, Id128)> {
let mut monotonic_timestamp_us: u64 = 0;
let mut id = Id128::default();
sd_try!(ffi::sd_journal_get_monotonic_usec(
self.j,
&mut monotonic_timestamp_us,
&mut id.inner,
));
Ok((monotonic_timestamp_us, id))
}
pub fn monotonic_timestamp_current_boot(&self) -> Result<u64> {
let mut monotonic_timestamp_us: u64 = 0;
sd_try!(ffi::sd_journal_get_monotonic_usec(
self.j,
&mut monotonic_timestamp_us,
ptr::null_mut(),
));
Ok(monotonic_timestamp_us)
}
pub fn match_add<T: Into<Vec<u8>>>(&mut self, key: &str, val: T) -> Result<&mut Journal> {
let mut filter = Vec::<u8>::from(key);
filter.push('=' as u8);
filter.extend(val.into());
let data = filter.as_ptr() as *const c_void;
let datalen = filter.len() as size_t;
sd_try!(ffi::sd_journal_add_match(self.j, data, datalen));
Ok(self)
}
pub fn match_or(&mut self) -> Result<&mut Journal> {
sd_try!(ffi::sd_journal_add_disjunction(self.j));
Ok(self)
}
pub fn match_and(&mut self) -> Result<&mut Journal> {
sd_try!(ffi::sd_journal_add_conjunction(self.j));
Ok(self)
}
pub fn match_flush(&mut self) -> Result<&mut Journal> {
unsafe { ffi::sd_journal_flush_matches(self.j) };
Ok(self)
}
}
impl Drop for Journal {
fn drop(&mut self) {
if !self.j.is_null() {
unsafe {
ffi::sd_journal_close(self.j);
}
}
}
}