mod enums;
pub mod iterators;
use chrono::{Duration, NaiveDateTime};
pub use enums::{CursorMovement, Enumeration, Error, Event, FileFlags, Level, NamespaceFlags,
PathFlags, UserFlags};
use iterators::{CursorIterator, CursorReverseIterator, FieldNames, Fields, UniqueValues};
use libc::{c_char, c_int, c_uchar, c_void, iovec, size_t};
use sd_id128::ID128;
use sd_sys::journal as ffi;
use std::{ffi::{CStr, CString},
fmt::Debug,
os::unix::io::RawFd,
path::PathBuf,
ptr};
#[derive(Debug)]
pub struct Journal {
ffi: *mut ffi::sd_journal
}
#[derive(Debug)]
pub struct Cursor<'a> {
pub(crate) journal: &'a Journal
}
impl Journal {
pub fn log_message<T: Into<Vec<u8>>>(level: Level, message: T) -> Result<(), Error> {
let c_message = CString::new(message).map_err(Error::NullError)?;
let result = unsafe { ffi::sd_journal_print(level as c_int, c_message.as_ptr()) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn log_raw_record<T: AsRef<[u8]>>(data: &[T]) -> Result<(), Error> {
let mut iovec_vec: Vec<iovec> = Vec::new();
for field in data {
let field = field.as_ref();
iovec_vec.push(iovec { iov_base: field.as_ptr() as *mut c_void,
iov_len: field.len() });
}
let result = unsafe { ffi::sd_journal_sendv(iovec_vec.as_ptr(), iovec_vec.len() as c_int) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn get_catalog_for_message_id(id: ID128) -> Result<String, Error> {
let mut data: *mut c_char = ptr::null_mut();
let result =
unsafe { ffi::sd_journal_get_catalog_for_message_id(id.into_ffi(), &mut data) };
if result < 0 {
return Err(Error::SDError(result));
}
let catalog = unsafe { CStr::from_ptr(data) };
let catalog = match catalog.to_str() {
Err(error) => {
unsafe { libc::free(data as *mut c_void) };
Err(Error::UTF8Error(error))?
},
Ok(value) => value.to_owned()
};
unsafe { libc::free(data as *mut c_void) };
Ok(catalog)
}
pub fn open(file_flags: FileFlags, user_flags: UserFlags) -> Result<Journal, Error> {
let mut pointer = ptr::null_mut() as *mut sd_sys::journal::sd_journal;
let flags = file_flags as c_int | user_flags as c_int;
let result = unsafe { ffi::sd_journal_open(&mut pointer, flags) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(Journal { ffi: pointer })
}
pub fn open_namespace<T: Into<Vec<u8>>>(namespace: T,
namespace_flags: NamespaceFlags,
file_flags: FileFlags,
user_flags: UserFlags)
-> Result<Journal, Error> {
let c_namespace = CString::new(namespace).map_err(Error::NullError)?;
let mut pointer = ptr::null_mut() as *mut ffi::sd_journal;
let flags = file_flags as c_int | user_flags as c_int | namespace_flags as c_int;
let result =
unsafe { ffi::sd_journal_open_namespace(&mut pointer, c_namespace.as_ptr(), flags) };
if result < 0 {
return Err(Error::SDError(result));
}
let journal = Journal { ffi: pointer };
Ok(journal)
}
pub fn open_all_namespaces(file_flags: FileFlags,
user_flags: UserFlags)
-> Result<Journal, Error> {
let mut pointer = ptr::null_mut() as *mut ffi::sd_journal;
let flags = file_flags as c_int | user_flags as c_int | ffi::SD_JOURNAL_ALL_NAMESPACES;
let result =
unsafe { ffi::sd_journal_open_namespace(&mut pointer, std::ptr::null(), flags) };
if result < 0 {
return Err(Error::SDError(result));
}
let journal = Journal { ffi: pointer };
Ok(journal)
}
pub fn open_directory<P: Into<PathBuf>>(path: P,
path_flags: PathFlags,
user_flags: UserFlags)
-> Result<Journal, Error> {
#[cfg(unix)]
use std::os::unix::ffi::OsStringExt;
let c_path =
CString::new(path.into().into_os_string().into_vec()).map_err(Error::NullError)?;
let mut pointer = ptr::null_mut() as *mut ffi::sd_journal;
let flags = path_flags as c_int | user_flags as c_int;
let result =
unsafe { ffi::sd_journal_open_directory(&mut pointer, c_path.as_ptr(), flags) };
if result < 0 {
return Err(Error::SDError(result));
}
let journal = Journal { ffi: pointer };
Ok(journal)
}
pub fn open_files<A: Into<Vec<P>>, P: Into<PathBuf>>(files: A) -> Result<Journal, Error> {
#[cfg(unix)]
use std::os::unix::ffi::OsStringExt;
let files: Vec<P> = files.into();
let mut c_files_vec: Vec<CString> = Vec::with_capacity(files.len());
for file in files {
let pb_file: PathBuf = file.into();
let os_file = pb_file.into_os_string();
c_files_vec.push(CString::new(os_file.into_vec()).map_err(Error::NullError)?);
}
let mut ptr_vec: Vec<*const c_char> =
c_files_vec.iter().map(|file| file.as_ptr()).collect();
ptr_vec.push(0 as *const c_char);
let mut pointer = std::ptr::null_mut() as *mut ffi::sd_journal;
let flags: c_int = 0;
let result = unsafe { ffi::sd_journal_open_files(&mut pointer, ptr_vec.as_ptr(), flags) };
if result < 0 {
return Err(Error::SDError(result));
}
let journal = Journal { ffi: pointer };
Ok(journal)
}
pub fn next(&self) -> Result<CursorMovement, Error> {
let result = unsafe { ffi::sd_journal_next(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(CursorMovement::EoF);
}
Ok(CursorMovement::Done)
}
pub fn iter(&self) -> CursorIterator {
CursorIterator { journal: &self }
}
pub fn previous(&self) -> Result<CursorMovement, Error> {
let result = unsafe { ffi::sd_journal_previous(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(CursorMovement::EoF);
}
Ok(CursorMovement::Done)
}
pub fn iter_reverse(&self) -> CursorReverseIterator {
CursorReverseIterator { journal: &self }
}
pub fn next_skip(&self, skip: c_int) -> Result<CursorMovement, Error> {
if skip < 0 {
return Err(Error::RangeError);
}
let result = unsafe { ffi::sd_journal_next_skip(self.ffi, skip as u64) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(CursorMovement::EoF);
}
if result < skip {
return Ok(CursorMovement::Limited(result));
}
Ok(CursorMovement::Done)
}
pub fn previous_skip(&self, skip: c_int) -> Result<CursorMovement, Error> {
if skip < 0 {
return Err(Error::RangeError);
}
let result = unsafe { ffi::sd_journal_previous_skip(self.ffi, skip as u64) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(CursorMovement::EoF);
}
if result < skip {
return Ok(CursorMovement::Limited(result));
}
Ok(CursorMovement::Done)
}
#[cfg(feature = "experimental")]
pub fn seek_head(&self) -> Result<(), Error> {
let result = unsafe { ffi::sd_journal_seek_head(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
#[cfg(feature = "experimental")]
pub fn seek_tail(&self) -> Result<(), Error> {
let result = unsafe { ffi::sd_journal_seek_tail(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
#[cfg(feature = "td_chrono")]
#[cfg(feature = "experimental")]
pub fn seek_monotonic(&self, boot_id: ID128, clock_monotonic: Duration) -> Result<(), Error> {
let usec: u64 = match clock_monotonic.num_microseconds() {
None => Err(Error::TimeStampOutOfRange)?,
Some(t) if t < 0 => Err(Error::TimeStampOutOfRange)?,
Some(t) => t as u64
};
let ffi_boot_id = boot_id.into_ffi();
let result = unsafe { ffi::sd_journal_seek_monotonic_usec(self.ffi, ffi_boot_id, usec) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
#[cfg(feature = "td_chrono")]
#[cfg(feature = "experimental")]
pub fn seek_realtime(&self, clock_realtime: NaiveDateTime) -> Result<(), Error> {
let usec = clock_realtime.timestamp_subsec_micros() as u64
+ clock_realtime.timestamp() as u64 * 1_000_000;
let result = unsafe { ffi::sd_journal_seek_realtime_usec(self.ffi, usec) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
#[cfg(feature = "experimental")]
pub fn seek_cursor_id(&self, cursor_id: String) -> Result<(), Error> {
let c_cursor = CString::new(cursor_id).map_err(Error::NullError)?;
let result = unsafe { ffi::sd_journal_seek_cursor(self.ffi, c_cursor.as_ptr()) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn add_match<T: AsRef<[c_uchar]>>(&self, filter: T) -> Result<(), Error> {
let filter = filter.as_ref();
let result = unsafe {
ffi::sd_journal_add_match(self.ffi, filter.as_ptr() as *const c_void, filter.len())
};
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn add_disjunction(&self) -> Result<(), Error> {
let result = unsafe { ffi::sd_journal_add_disjunction(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn add_conjunction(&self) -> Result<(), Error> {
let result = unsafe { ffi::sd_journal_add_conjunction(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn flush_matches(&self) {
unsafe { ffi::sd_journal_flush_matches(self.ffi) }
}
#[cfg(feature = "td_chrono")]
#[cfg(feature = "experimental")]
pub fn get_realtime_cutoff(&self) -> Result<(NaiveDateTime, NaiveDateTime), Error> {
let mut from_usec: u64 = 0;
let mut to_usec: u64 = 0;
let result = unsafe {
ffi::sd_journal_get_cutoff_realtime_usec(self.ffi, &mut from_usec, &mut to_usec)
};
if result < 0 {
return Err(Error::SDError(result));
}
let from = NaiveDateTime::from_timestamp((from_usec / 1_000_000) as i64,
((from_usec % 1_000_000) * 1_000) as u32);
let to = NaiveDateTime::from_timestamp((to_usec / 1_000_000) as i64,
((to_usec % 1_000_000) * 1_000) as u32);
Ok((from, to))
}
#[cfg(feature = "td_chrono")]
#[cfg(feature = "experimental")]
pub fn get_monotonic_cutoff(&self, boot_id: ID128) -> Result<(Duration, Duration), Error> {
let mut from_usec: u64 = 0;
let mut to_usec: u64 = 0;
let result = unsafe {
ffi::sd_journal_get_cutoff_monotonic_usec(self.ffi,
boot_id.into_ffi(),
&mut from_usec,
&mut to_usec)
};
if result < 0 {
return Err(Error::SDError(result));
}
let from = Duration::seconds((from_usec / 1_000_000) as i64)
+ Duration::microseconds((from_usec % 1_000_000) as i64);
let to = Duration::seconds((to_usec / 1_000_000) as i64)
+ Duration::microseconds((to_usec % 1_000_000) as i64);
Ok((from, to))
}
pub fn set_data_treshold(&self, size: size_t) -> Result<(), Error> {
let result = unsafe { ffi::sd_journal_set_data_threshold(self.ffi, size) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn get_data_treshold(&self) -> Result<size_t, Error> {
let mut size: size_t = 0;
let result = unsafe { ffi::sd_journal_get_data_threshold(self.ffi, &mut size) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(size)
}
pub fn enumerate_field_names(&self) -> Result<Enumeration<String>, Error> {
let mut field: *const c_char = ptr::null();
let result = unsafe { ffi::sd_journal_enumerate_fields(self.ffi, &mut field) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(Enumeration::EoF);
}
Ok(Enumeration::Value(unsafe {
CStr::from_ptr(field).to_str()
.map_err(Error::UTF8Error)?
.to_owned()
}))
}
pub fn restart_field_name_enumeration(&self) {
unsafe { ffi::sd_journal_restart_fields(self.ffi) }
}
pub fn iter_field_names<'a>(&'a self) -> FieldNames<'a> {
FieldNames { journal: self }
}
pub fn get_fd(&self) -> Result<RawFd, Error> {
let result = unsafe { ffi::sd_journal_get_fd(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(result)
}
pub fn get_events(&self) -> Result<c_int, Error> {
let result = unsafe { ffi::sd_journal_get_fd(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(result)
}
pub fn get_timeout(&self) -> Result<u64, Error> {
let mut timeout: u64 = 0;
let result = unsafe { ffi::sd_journal_get_timeout(self.ffi, &mut timeout) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(timeout)
}
pub fn process(&self) -> Result<Event, Error> {
let result = unsafe { ffi::sd_journal_process(self.ffi) };
match result {
ffi::SD_JOURNAL_NOP => Ok(Event::NOOP),
ffi::SD_JOURNAL_APPEND => Ok(Event::Append),
ffi::SD_JOURNAL_INVALIDATE => Ok(Event::Invalidate),
_ => Err(Error::SDError(result))
}
}
pub fn wait(&self, timeout: u64) -> Result<Event, Error> {
let result = unsafe { ffi::sd_journal_wait(self.ffi, timeout) };
match result {
ffi::SD_JOURNAL_NOP => Ok(Event::NOOP),
ffi::SD_JOURNAL_APPEND => Ok(Event::Append),
ffi::SD_JOURNAL_INVALIDATE => Ok(Event::Invalidate),
_ => Err(Error::SDError(result))
}
}
pub fn has_runtime_files(&self) -> Result<bool, Error> {
let result = unsafe { ffi::sd_journal_has_runtime_files(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(result > 0)
}
pub fn has_persistent_files(&self) -> Result<bool, Error> {
let result = unsafe { ffi::sd_journal_has_persistent_files(self.ffi) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(result > 0)
}
#[cfg(feature = "experimental")]
pub fn get_usage(&self) -> Result<u64, Error> {
let mut usage: u64 = 0;
let result = unsafe { ffi::sd_journal_get_usage(self.ffi, &mut usage) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(usage)
}
#[cfg(feature = "td_chrono")]
#[cfg(feature = "experimental")]
pub fn get_realtime(&self) -> Result<NaiveDateTime, Error> {
let mut usec: u64 = 0;
let result = unsafe { ffi::sd_journal_get_realtime_usec(self.ffi, &mut usec) };
if result < 0 {
return Err(Error::SDError(result));
}
let dt = NaiveDateTime::from_timestamp((usec / 1_000_000) as i64,
((usec % 1_000_000) * 1_000) as u32);
Ok(dt)
}
#[cfg(feature = "td_chrono")]
#[cfg(feature = "experimental")]
pub fn get_monotonic(&self) -> Result<(Duration, sd_id128::ID128), Error> {
let mut usec: u64 = 0;
let mut boot_id = ID128::default().into_ffi();
let result =
unsafe { ffi::sd_journal_get_monotonic_usec(self.ffi, &mut usec, &mut boot_id) };
if result < 0 {
return Err(Error::SDError(result));
}
let duration = Duration::seconds((usec / 1_000_000) as i64)
+ Duration::microseconds((usec % 1_000_000) as i64);
Ok((duration, ID128::from_ffi(boot_id)))
}
#[cfg(feature = "experimental")]
pub fn get_cursor_id(&self) -> Result<String, Error> {
let mut ptr: *mut c_char = ptr::null_mut();
let result = unsafe { ffi::sd_journal_get_cursor(self.ffi, &mut ptr) };
if result < 0 {
return Err(Error::SDError(result));
}
let cursor_id = unsafe { CStr::from_ptr(ptr) };
let cursor_id = match cursor_id.to_str() {
Err(error) => {
unsafe { libc::free(ptr as *mut c_void) };
Err(Error::UTF8Error(error))?
},
Ok(value) => value.to_owned()
};
unsafe { libc::free(ptr as *mut c_void) };
Ok(cursor_id)
}
#[cfg(feature = "experimental")]
pub fn cursor_id_matches<S: Into<Vec<u8>>>(&self, cursor_id: S) -> Result<bool, Error> {
let c_cursor = CString::new(cursor_id).map_err(Error::NullError)?;
let result = unsafe { ffi::sd_journal_test_cursor(self.ffi, c_cursor.as_ptr()) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(result > 0)
}
pub fn get_catalog(&self) -> Result<String, Error> {
let mut data: *mut c_char = ptr::null_mut();
let result = unsafe { ffi::sd_journal_get_catalog(self.ffi, &mut data) };
if result < 0 {
return Err(Error::SDError(result));
}
let catalog = unsafe { CStr::from_ptr(data) };
let catalog = match catalog.to_str() {
Err(error) => {
unsafe { libc::free(data as *mut c_void) };
Err(Error::UTF8Error(error))?
},
Ok(value) => value.to_owned()
};
unsafe { libc::free(data as *mut c_void) };
Ok(catalog)
}
pub fn get_data<F: Into<Vec<u8>>>(&self, field: F) -> Result<String, Error> {
let c_field = CString::new(field).map_err(Error::NullError)?;
let mut data: *const c_void = std::ptr::null_mut();
let mut length: size_t = 0;
let result =
unsafe { ffi::sd_journal_get_data(self.ffi, c_field.as_ptr(), &mut data, &mut length) };
if result < 0 {
return Err(Error::SDError(result));
}
let result = unsafe {
CStr::from_ptr(data as *mut c_char).to_str()
.map_err(Error::UTF8Error)?
};
let field = c_field.into_string().map_err(Error::StringError)?;
let result = match result.strip_prefix(&field) {
None => Err(Error::UnexpectedDataFormat)?,
Some(value) => match value.strip_prefix('=') {
None => Err(Error::UnexpectedDataFormat)?,
Some(value) => value
}
};
Ok(result.to_string())
}
pub fn enumerate_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
let mut data: *const c_void = ptr::null_mut();
let mut length: size_t = 0;
let result = unsafe { ffi::sd_journal_enumerate_data(self.ffi, &mut data, &mut length) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(Enumeration::EoF);
}
let result = unsafe {
CStr::from_ptr(data as *const c_char).to_str()
.map_err(Error::UTF8Error)?
.to_owned()
};
let (field, value) = match result.find('=') {
None => Err(Error::UnexpectedDataFormat)?,
Some(index) => result.split_at(index)
};
let value = match value.strip_prefix('=') {
None => Err(Error::UnexpectedDataFormat)?,
Some(value) => value
};
Ok(Enumeration::Value((field.to_owned(), value.to_owned())))
}
pub fn enumerate_available_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
let mut data: *const c_void = ptr::null_mut();
let mut length: size_t = 0;
let result =
unsafe { ffi::sd_journal_enumerate_available_data(self.ffi, &mut data, &mut length) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(Enumeration::EoF);
}
if result == 0 {
return Ok(Enumeration::EoF);
}
let result = unsafe {
CStr::from_ptr(data as *const c_char).to_str()
.map_err(Error::UTF8Error)?
.to_owned()
};
let (field, value) = match result.find('=') {
None => Err(Error::UnexpectedDataFormat)?,
Some(index) => result.split_at(index)
};
let value = match value.strip_prefix('=') {
None => Err(Error::UnexpectedDataFormat)?,
Some(value) => value
};
Ok(Enumeration::Value((field.to_owned(), value.to_owned())))
}
pub fn restart_fields_enumeration(&self) {
unsafe {
ffi::sd_journal_restart_data(self.ffi);
}
}
pub fn iter_fields<'a>(&'a self) -> Fields<'a> {
Fields { journal: self }
}
pub fn query_unique_values<S: Into<Vec<u8>>>(&self, field: S) -> Result<(), Error> {
let c_field = CString::new(field).map_err(Error::NullError)?;
let result = unsafe { ffi::sd_journal_query_unique(self.ffi, c_field.as_ptr()) };
if result < 0 {
return Err(Error::SDError(result));
}
Ok(())
}
pub fn enumerate_unique_values(&self) -> Result<Enumeration<String>, Error> {
let mut data: *const c_void = ptr::null_mut();
let mut length: size_t = 0;
let result = unsafe { ffi::sd_journal_enumerate_unique(self.ffi, &mut data, &mut length) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(Enumeration::EoF);
}
let result = unsafe {
CStr::from_ptr(data as *const c_char).to_str()
.map_err(Error::UTF8Error)?
};
let index = match result.find('=') {
None => Err(Error::UnexpectedDataFormat)?,
Some(index) => index
};
let (_, result) = result.split_at(index + 1);
Ok(Enumeration::Value(result.to_owned()))
}
pub fn enumerate_available_unique_values(&self) -> Result<Enumeration<String>, Error> {
let mut data: *const c_void = ptr::null_mut();
let mut length: size_t = 0;
let result =
unsafe { ffi::sd_journal_enumerate_available_unique(self.ffi, &mut data, &mut length) };
if result < 0 {
return Err(Error::SDError(result));
}
if result == 0 {
return Ok(Enumeration::EoF);
}
Ok(Enumeration::Value(unsafe {
CStr::from_ptr(data as *const c_char).to_str()
.map_err(Error::UTF8Error)?
.to_owned()
}))
}
pub fn restart_unique_value_enumeration(&self) {
unsafe { ffi::sd_journal_restart_unique(self.ffi) }
}
pub fn iter_unique_values<'a, S: Into<Vec<u8>>>(&'a self,
field: S)
-> Result<UniqueValues<'a>, Error> {
self.query_unique_values(field)?;
Ok(UniqueValues { journal: &self })
}
}
impl<'a> Cursor<'a> {
pub fn get_realtime(&self) -> Result<NaiveDateTime, Error> {
self.journal.get_realtime()
}
pub fn get_monotonic(&self) -> Result<(Duration, sd_id128::ID128), Error> {
self.journal.get_monotonic()
}
pub fn get_id(&self) -> Result<String, Error> {
self.journal.get_cursor_id()
}
pub fn id_matches<S: Into<Vec<u8>>>(&self, cursor_id: S) -> Result<bool, Error> {
self.journal.cursor_id_matches(cursor_id)
}
pub fn get_catalog(&self) -> Result<String, Error> {
self.journal.get_catalog()
}
pub fn get_data<F: Into<Vec<u8>>>(&self, field: F) -> Result<String, Error> {
self.journal.get_data(field)
}
pub fn enumerate_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
self.journal.enumerate_fields()
}
pub fn enumerate_available_fields(&self) -> Result<Enumeration<(String, String)>, Error> {
self.journal.enumerate_available_fields()
}
pub fn restart_fields_enumeration(&self) {
self.journal.restart_fields_enumeration()
}
pub fn iter_fields(&self) -> Fields<'a> {
self.journal.iter_fields()
}
}