use libc::{c_char, c_int, size_t};
use log::{self, Log, LogRecord, LogLocation, LogLevel, LogLevelFilter, SetLoggerError};
use std::{fmt, io, ptr, result};
use std::collections::BTreeMap;
use std::ffi::CString;
use std::io::ErrorKind::InvalidData;
use std::os::raw::c_void;
use ffi::array_to_iovecs;
use ffi::id128::sd_id128_t;
use ffi::journal as ffi;
use id128::Id128;
use super::Result;
use std::time;
use mbox::MString;
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_record(record: &LogRecord) {
let lvl = match record.level() {
LogLevel::Error => SyslogLevel::Err,
LogLevel::Warn => SyslogLevel::Warning,
LogLevel::Info => SyslogLevel::Info,
LogLevel::Debug |
LogLevel::Trace => SyslogLevel::Debug,
} as usize;
log_target(lvl, record.location(), record.args(), record.target());
}
pub fn log(level: usize, loc: &LogLocation, args: &fmt::Arguments) {
send(&[&format!("PRIORITY={}", level),
&format!("MESSAGE={}", args),
&format!("CODE_LINE={}", loc.line()),
&format!("CODE_FILE={}", loc.file()),
&format!("CODE_FUNCTION={}", loc.module_path())]);
}
pub fn log_target(level: usize, loc: &LogLocation, args: &fmt::Arguments, target: &str) {
send(&[&format!("PRIORITY={}", level),
&format!("MESSAGE={}", args),
&format!("TARGET={}", target),
&format!("CODE_LINE={}", loc.line()),
&format!("CODE_FILE={}", loc.file()),
&format!("CODE_FUNCTION={}", loc.module_path())]);
}
pub struct JournalLog;
impl Log for JournalLog {
fn enabled(&self, _metadata: &log::LogMetadata) -> bool {
true
}
fn log(&self, record: &LogRecord) {
log_record(record);
}
}
impl JournalLog {
pub fn init() -> result::Result<(), SetLoggerError> {
Self::init_with_level(LogLevelFilter::Info)
}
pub fn init_with_level(level: LogLevelFilter) -> result::Result<(), SetLoggerError> {
log::set_logger(|max_log_level| {
max_log_level.set(level);
Box::new(JournalLog)
})
}
}
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 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,
}
pub enum JournalFiles {
System,
CurrentUser,
All,
}
pub enum JournalSeek {
Head,
Current,
Tail,
ClockMonotonic {
boot_id: Id128,
usec: u64,
},
ClockRealtime {
usec: u64,
},
Cursor {
cursor: String,
},
}
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 = ::std::str::from_utf8_unchecked(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()
}
pub fn seek(&mut self, seek: JournalSeek) -> Result<String> {
match seek {
JournalSeek::Head => sd_try!(ffi::sd_journal_seek_head(self.j)),
JournalSeek::Current => 0,
JournalSeek::Tail => 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 } {
sd_try!(ffi::sd_journal_next(self.j));
sd_try!(ffi::sd_journal_get_cursor(self.j, &c));
}
let cs = unsafe { MString::from_raw(c) };
let cs = try!(cs.or(Err(io::Error::new(InvalidData, "invalid cursor"))));
Ok(cs.to_string())
}
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 = unsafe { MString::from_raw(c_cursor) };
let cursor = try!(cursor.or(Err(io::Error::new(InvalidData, "invalid cursor"))));
Ok(cursor.to_string())
}
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 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);
}
}
}
}