From 0c4def8e7fe140ee8fbd32528a9b2013e004190b Mon Sep 17 00:00:00 2001 From: Grzegorz Nowakowski Date: Fri, 6 Dec 2024 18:26:37 +0100 Subject: [PATCH] Initial implementation Change-Id: I222f3d4f781a234c29288b3178f74010e719c79e --- dlog-redirect-stdout-sys/src/lib.rs | 6 + dlog-redirect-stdout/src/lib.rs | 103 ++++ dlog-sys/src/lib.rs | 41 ++ dlog/src/lib.rs | 228 +++++++ dlogutil-sys/src/lib.rs | 114 ++++ dlogutil/src/lib.rs | 823 ++++++++++++++++++++++++++ packaging/rust-dlog.manifest | 5 + packaging/rust-dlog.spec | 216 +++++++ tests/src/dlogsend.rs | 22 + tests/src/logutil.rs | 96 +++ tests/src/test_dlog_critical_print.rs | 58 ++ tests/src/test_dlog_custom_tag.rs | 36 ++ tests/src/test_dlog_print.rs | 58 ++ tests/src/test_dlog_redirect.rs | 41 ++ 14 files changed, 1847 insertions(+) create mode 100644 dlog-redirect-stdout-sys/src/lib.rs create mode 100644 dlog-redirect-stdout/src/lib.rs create mode 100644 dlog-sys/src/lib.rs create mode 100644 dlog/src/lib.rs create mode 100644 dlogutil-sys/src/lib.rs create mode 100644 dlogutil/src/lib.rs create mode 100644 packaging/rust-dlog.manifest create mode 100644 packaging/rust-dlog.spec create mode 100644 tests/src/dlogsend.rs create mode 100644 tests/src/logutil.rs create mode 100644 tests/src/test_dlog_critical_print.rs create mode 100644 tests/src/test_dlog_custom_tag.rs create mode 100644 tests/src/test_dlog_print.rs create mode 100644 tests/src/test_dlog_redirect.rs diff --git a/dlog-redirect-stdout-sys/src/lib.rs b/dlog-redirect-stdout-sys/src/lib.rs new file mode 100644 index 0000000..c6ac8f6 --- /dev/null +++ b/dlog-redirect-stdout-sys/src/lib.rs @@ -0,0 +1,6 @@ +use std::ffi::{c_char, c_int}; + +extern "C" { + pub fn dlog_connect_fd(buffer: i32, fileno: c_int, tag: *const c_char, prio: i32) -> c_int; + pub fn dlog_is_log_fd(fd: c_int) -> bool; +} diff --git a/dlog-redirect-stdout/src/lib.rs b/dlog-redirect-stdout/src/lib.rs new file mode 100644 index 0000000..92cbebc --- /dev/null +++ b/dlog-redirect-stdout/src/lib.rs @@ -0,0 +1,103 @@ +use dlog::{BufferId, Priority}; +use dlog_redirect_stdout_sys::*; +use std::ffi::CStr; +use std::io::{Error, ErrorKind, Result}; + +/// Open an unstructured connection to a DLog logging sink. +/// +/// Opens a connection with stdout to which you can stream data. +/// DLog will automatically convert it to individual logs appropriately. +/// +/// # Arguments +/// +/// * `buffer` - Target DLog buffer. +/// * `tag` - DLog tag for the resulting logs. +/// * `prio` - Priority for the resulting logs. +/// +/// # Errors +/// +/// * [`std::io::ErrorKind::InvalidInput`] - Buffer does not allow connections. +/// * [`std::io::ErrorKind::PermissionDenied`] - Buffer disabled via dlog config. +/// * [`std::io::ErrorKind::Uncategorized`] - Buffer not configured via dlog config. +/// * [`std::io::ErrorKind::Uncategorized`] - Unsupported DLog backend. +/// * [`std::io::ErrorKind::InvalidInput`] - Tag empty. +/// +/// # Examples +/// +/// ``` +/// use dlog::{BufferId, Priority}; +/// use dlog_redirect_stdout::redirect_stdout; +/// +/// fn main() -> Result<(), std::io::Error> { +/// redirect_stdout(BufferId::LogIdMain, "foo", Priority::Info)?; +/// // The following sends an info log with tag "foo" and message "my_message". +/// println!("my_message"); +/// Ok(()) +/// } +/// ``` +pub fn redirect_stdout(buffer: BufferId, tag: &str, prio: Priority) -> Result<()> { + let tag = format!("{}\0", tag); + let tag_c = CStr::from_bytes_with_nul(tag.as_bytes()).map_err(|_| ErrorKind::InvalidInput)?; + // SAFETY: the string is defined in a verified way above. All other parameters + // are checked for correctness on the C side. + match unsafe { dlog_connect_fd(buffer as i32, 1, tag_c.as_ptr(), prio as i32) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } +} + +/// Open an unstructured connection to a DLog logging sink. +/// +/// Opens a connection with stderr to which you can stream data. +/// DLog will automatically convert it to individual logs appropriately. +/// +/// # Arguments +/// +/// * `buffer` - Target DLog buffer. +/// * `tag` - DLog tag for the resulting logs. +/// * `prio` - Priority for the resulting logs. +/// +/// # Errors +/// +/// * [`std::io::ErrorKind::InvalidInput`] - Buffer does not allow connections. +/// * [`std::io::ErrorKind::PermissionDenied`] - Buffer disabled via dlog config. +/// * [`std::io::ErrorKind::Uncategorized`] - Buffer not configured via dlog config. +/// * [`std::io::ErrorKind::Uncategorized`] - Unsupported DLog backend. +/// * [`std::io::ErrorKind::InvalidInput`] - Tag empty. +/// +/// # Examples +/// +/// ``` +/// use dlog::{BufferId, Priority}; +/// use dlog_redirect_stdout::redirect_stderr; +/// +/// fn main() -> Result<(), std::io::Error> { +/// redirect_stderr(BufferId::LogIdMain, "foo", Priority::Info)?; +/// // The following sends an info log with tag "foo" and message "my_message". +/// eprintln!("my_message"); +/// Ok(()) +/// } +/// ``` +pub fn redirect_stderr(buffer: BufferId, tag: &str, prio: Priority) -> Result<()> { + let tag = format!("{}\0", tag); + let tag_c = CStr::from_bytes_with_nul(tag.as_bytes()).map_err(|_| ErrorKind::InvalidInput)?; + // SAFETY: the string is defined in a verified way above. All other parameters + // are checked for correctness on the C side. + match unsafe { dlog_connect_fd(buffer as i32, 2, tag_c.as_ptr(), prio as i32) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } +} + +/// Check whether a file descriptor has been opened via DLog. +/// +/// Use for low-level code that does bulk operations on FDs (for example, close +/// them all) but still wants to be able to use DLog interfaces afterwards. +/// +/// # Returns +/// +/// * `bool` - Whether the descriptor refers to a DLog file. +pub fn is_log_fd(fd: i32) -> bool { + // SAFETY: the C function is safe by itself. + unsafe { dlog_is_log_fd(fd) } +} diff --git a/dlog-sys/src/lib.rs b/dlog-sys/src/lib.rs new file mode 100644 index 0000000..8e5352c --- /dev/null +++ b/dlog-sys/src/lib.rs @@ -0,0 +1,41 @@ +use std::ffi::{c_char, c_int}; + +#[repr(C)] +#[allow(non_camel_case_types)] +pub enum log_priority { + DLOG_UNKNOWN = 0, + DLOG_DEFAULT, + DLOG_VERBOSE, + DLOG_DEBUG, + DLOG_INFO, + DLOG_WARN, + DLOG_ERROR, + DLOG_FATAL, + DLOG_SILENT, + DLOG_PRIO_MAX, +} + +#[repr(C)] +#[allow(non_camel_case_types)] +pub enum log_id_t { + LOG_ID_INVALID = -1, + LOG_ID_MAIN, + LOG_ID_RADIO, + LOG_ID_SYSTEM, + LOG_ID_APPS, + LOG_ID_KMSG, + LOG_ID_SYSLOG, + LOG_ID_MAX, +} + +extern "C" { + pub fn dlog_print(prio: log_priority, tag: *const c_char, fmt: *const c_char, ...) -> c_int; + pub fn __dlog_critical_print( + log_id: log_id_t, + prio: c_int, + tag: *const c_char, + fmt: *const c_char, + ... + ) -> c_int; + pub fn dlog_set_minimum_priority(priority: c_int) -> c_int; +} diff --git a/dlog/src/lib.rs b/dlog/src/lib.rs new file mode 100644 index 0000000..c2d7b35 --- /dev/null +++ b/dlog/src/lib.rs @@ -0,0 +1,228 @@ +use dlog_sys::*; +use log::{Level, LevelFilter, Log, Metadata, Record}; +use std::ffi::CStr; +use std::io::{Error, Result}; + +/// Enumeration for log priority values in ascending priority order. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum Priority { + Unknown = 0, + Default, + Verbose, + Debug, + Info, + Warn, + Error, + Fatal, + Silent, + PrioMax, +} + +/// Log id. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum BufferId { + LogIdMain = 0, + LogIdRadio, + LogIdSystem, + LogIdApps, + LogIdKmsg, + LogIdSyslog, +} + +impl From for Priority { + fn from(c_enum: log_priority) -> Self { + match c_enum { + log_priority::DLOG_UNKNOWN => Priority::Unknown, + log_priority::DLOG_DEFAULT => Priority::Default, + log_priority::DLOG_VERBOSE => Priority::Verbose, + log_priority::DLOG_DEBUG => Priority::Debug, + log_priority::DLOG_INFO => Priority::Info, + log_priority::DLOG_WARN => Priority::Warn, + log_priority::DLOG_ERROR => Priority::Error, + log_priority::DLOG_FATAL => Priority::Fatal, + log_priority::DLOG_SILENT => Priority::Silent, + log_priority::DLOG_PRIO_MAX => Priority::PrioMax, + } + } +} + +impl From for BufferId { + fn from(c_enum: log_id_t) -> Self { + match c_enum { + log_id_t::LOG_ID_MAIN => BufferId::LogIdMain, + log_id_t::LOG_ID_RADIO => BufferId::LogIdRadio, + log_id_t::LOG_ID_SYSTEM => BufferId::LogIdSystem, + log_id_t::LOG_ID_APPS => BufferId::LogIdApps, + log_id_t::LOG_ID_KMSG => BufferId::LogIdKmsg, + log_id_t::LOG_ID_SYSLOG => BufferId::LogIdSyslog, + _ => panic!("Invalid log_id_t"), + } + } +} + +impl From for log_id_t { + fn from(rust_enum: BufferId) -> Self { + match rust_enum { + BufferId::LogIdMain => log_id_t::LOG_ID_MAIN, + BufferId::LogIdRadio => log_id_t::LOG_ID_RADIO, + BufferId::LogIdSystem => log_id_t::LOG_ID_SYSTEM, + BufferId::LogIdApps => log_id_t::LOG_ID_APPS, + BufferId::LogIdKmsg => log_id_t::LOG_ID_KMSG, + BufferId::LogIdSyslog => log_id_t::LOG_ID_SYSLOG, + } + } +} + +struct DlogLogger { + is_critical: bool, +} + +impl Log for DlogLogger { + fn enabled(&self, _meta: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + let target = record.target(); + let file = record + .file() + .map(|s| s.to_owned()) + .unwrap_or_else(|| "?".to_owned()); + let line = record + .line() + .map(|l| l.to_string()) + .unwrap_or_else(|| "?".to_owned()); + let args = record.args(); + + let prio = match record.level() { + Level::Error => log_priority::DLOG_ERROR, + Level::Warn => log_priority::DLOG_WARN, + Level::Info => log_priority::DLOG_INFO, + Level::Debug => log_priority::DLOG_DEBUG, + Level::Trace => log_priority::DLOG_VERBOSE, + }; + + let tag = target.to_string().replace('\0', "\\0") + "\0"; + let contents = format!("{}({}) > {}", file, line, args).replace('\0', "\\0") + "\0"; + + // SAFETY: the three _unchecked calls are ok, since we make sure that the + // three strings only have a single \0 and it is at the end. Then the dlog + // call is called with verified parameters, which is completely safe. + unsafe { + let tag_c = CStr::from_bytes_with_nul_unchecked(tag.as_bytes()); + let fmt_c = CStr::from_bytes_with_nul_unchecked(b"%s\0"); + let contents_c = CStr::from_bytes_with_nul_unchecked(contents.as_bytes()); + // TODO: What about structured values? + if self.is_critical { + __dlog_critical_print( + BufferId::LogIdMain.into(), + prio as i32, + tag_c.as_ptr(), + fmt_c.as_ptr(), + contents_c.as_ptr(), + ); + } else { + dlog_print(prio, tag_c.as_ptr(), fmt_c.as_ptr(), contents_c.as_ptr()); + } + } + } + + fn flush(&self) {} +} + +/// Initializes the DLog logging system. +/// +/// This function sets up the logging infrastructure by configuring the logger +/// for the current application. **It should be called once**. +/// +/// Under the hood, there is a struct with [`Log`] trait implemented that uses +/// `dlog_print` for sending logs to DLog. If invoked with `is_critical = true`, +/// it sends critical logs instead. +/// +/// Priority of a log is defined by a macro used and tag is constructed from +/// the binary name. However, it can be overwritten - see example below. +/// +/// [`Log`]: log::Log +/// +/// # Errors +/// +/// * [`std::io::ErrorKind::Other`] - Wrapped error from [`set_boxed_logger`]. +/// +/// [`set_boxed_logger`]: log::set_boxed_logger +/// +/// # Examples +/// +/// Sending non-critical logs: +/// +/// ``` +/// use log::info; +/// +/// fn main() -> Result<(), std::io::Error> { +/// dlog::init(false)?; +/// info!("my first log"); +/// } +/// ``` +/// +/// Sending critical logs: +/// +/// ``` +/// use log::info; +/// +/// fn main() -> Result<(), std::io::Error> { +/// dlog::init(true)?; +/// info!("my first critical log"); +/// } +/// ``` +/// +/// You can set your custom tag by setting `target` argument: +/// +/// ``` +/// use log::info; +/// +/// fn main() -> Result<(), std::io::Error> { +/// dlog::init(false)?; +/// info!(target: "my_tag", "my first log with a custom tag"); +/// } +/// ``` +pub fn init(is_critical: bool) -> Result<()> { + log::set_boxed_logger(Box::new(DlogLogger { is_critical })).map_err(Error::other)?; + log::set_max_level(LevelFilter::Trace); + Ok(()) +} + +/// Blocks low priority logs. +/// +/// Allows filtering low priority logs globally, think a runtime "--verbose" mode, +/// except you can choose which priorities to filter. +/// Using this (as opposed to filtering later, when retrieving logs) is that it +/// helps conserve system logging resources (buffer space etc.). +/// +/// # Arguments +/// +/// * `prio` - Minimum priority level. +/// +/// # Examples +/// +/// ``` +/// use dlog::Priority; +/// use log::{error, info, warn}; +/// +/// fn main() -> Result<(), std::io::Error> { +/// dlog::init(false)?; +/// info!("no filter so this log goes through"); +/// dlog::set_minimum_priority(Priority::Warn)?; +/// info!("WARN is a higher level than INFO so this log gets filtered"); +/// warn!("but this one still goes through"); +/// error!("as does this one"); +/// Ok(()) +/// } +/// ``` +pub fn set_minimum_priority(prio: Priority) -> Result<()> { + // SAFETY: the underlying C function is safe by itself, even if the parameter + // is out of range. The parameter will be in range in our case anyway, since + // our enum matches the C one. + match unsafe { dlog_set_minimum_priority(prio as i32) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } +} diff --git a/dlogutil-sys/src/lib.rs b/dlogutil-sys/src/lib.rs new file mode 100644 index 0000000..5c8868d --- /dev/null +++ b/dlogutil-sys/src/lib.rs @@ -0,0 +1,114 @@ +use dlog_sys::{log_id_t, log_priority}; +use libc::timespec; +use std::ffi::{c_char, c_int, c_uint}; + +#[repr(C)] +#[allow(non_camel_case_types)] +pub enum dlogutil_sorting_order { + DLOGUTIL_SORT_SENT_MONO = 0, + DLOGUTIL_SORT_SENT_REAL, + DLOGUTIL_SORT_RECV_MONO, + DLOGUTIL_SORT_RECV_REAL, + DLOGUTIL_SORT_DEFAULT, +} + +#[repr(C)] +pub struct dlogutil_config { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +#[repr(C)] +pub struct dlogutil_state { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +#[repr(C)] +pub struct dlogutil_entry { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} + +extern "C" { + pub fn dlogutil_config_create() -> *mut dlogutil_config; + pub fn dlogutil_config_destroy(config: *mut dlogutil_config); + pub fn dlogutil_config_filter_tid(config: *mut dlogutil_config, tid: c_int) -> c_int; + pub fn dlogutil_config_filter_pid(config: *mut dlogutil_config, pid: c_int) -> c_int; + pub fn dlogutil_config_filter_filterspec( + config: *mut dlogutil_config, + query: *const c_char, + ) -> c_int; + pub fn dlogutil_config_sorting_disable(config: *mut dlogutil_config) -> c_int; + pub fn dlogutil_config_sorting_enable(config: *mut dlogutil_config) -> c_int; + pub fn dlogutil_config_sorting_enable_with_size( + config: *mut dlogutil_config, + entry_count: c_uint, + ) -> c_int; + pub fn dlogutil_config_order_set( + config: *mut dlogutil_config, + sort_by: dlogutil_sorting_order, + ) -> c_int; + pub fn dlogutil_config_buffer_add(config: *mut dlogutil_config, buf: log_id_t) -> c_int; + pub fn dlogutil_state_destroy(state: *mut dlogutil_state); + pub fn dlogutil_config_connect( + config: *mut dlogutil_config, + out_state: *mut *mut dlogutil_state, + ) -> c_int; + pub fn dlogutil_config_mode_set_continuous(config: *mut dlogutil_config) -> c_int; + pub fn dlogutil_config_mode_set_monitor(config: *mut dlogutil_config) -> c_int; + pub fn dlogutil_config_mode_set_dump( + config: *mut dlogutil_config, + entry_count: c_uint, + ) -> c_int; + pub fn dlogutil_config_mode_set_compressed_memory_dump( + config: *mut dlogutil_config, + compress_buffer: *const c_char, + ) -> c_int; + pub fn dlogutil_get_log( + state: *mut dlogutil_state, + timeout: c_int, + entry_out: *mut *mut dlogutil_entry, + ) -> c_int; + pub fn dlogutil_buffer_clear(state: *mut dlogutil_state, buffer: log_id_t) -> c_int; + pub fn dlogutil_buffer_get_name(buffer: log_id_t, name: *mut *const c_char) -> c_int; + pub fn dlogutil_buffer_get_capacity( + state: *mut dlogutil_state, + buffer: log_id_t, + capacity: *mut c_uint, + ) -> c_int; + pub fn dlogutil_buffer_get_usage( + state: *mut dlogutil_state, + buffer: log_id_t, + usage: *mut c_uint, + ) -> c_int; + pub fn dlogutil_buffer_get_default_ts_type( + buffer: log_id_t, + typ: *mut dlogutil_sorting_order, + ) -> c_int; + pub fn dlogutil_buffer_check_ts_type_available( + buffer: log_id_t, + typ: dlogutil_sorting_order, + available: *mut bool, + ) -> c_int; + pub fn dlogutil_buffer_get_alias( + state: *mut dlogutil_state, + buffer: log_id_t, + real_buffer: *mut log_id_t, + ) -> c_int; + pub fn dlogutil_entry_destroy(entry: *mut dlogutil_entry); + pub fn dlogutil_entry_get_timestamp( + entry: *const dlogutil_entry, + stamp_type: dlogutil_sorting_order, + ts: *mut timespec, + ) -> c_int; + pub fn dlogutil_entry_get_tid(entry: *const dlogutil_entry, tid: *mut c_int) -> c_int; + pub fn dlogutil_entry_get_pid(entry: *const dlogutil_entry, pid: *mut c_int) -> c_int; + pub fn dlogutil_entry_get_priority( + entry: *const dlogutil_entry, + prio: *mut log_priority, + ) -> c_int; + pub fn dlogutil_entry_get_tag(entry: *const dlogutil_entry, tag: *mut *const c_char) -> c_int; + pub fn dlogutil_entry_get_message( + entry: *const dlogutil_entry, + msg: *mut *const c_char, + ) -> c_int; +} diff --git a/dlogutil/src/lib.rs b/dlogutil/src/lib.rs new file mode 100644 index 0000000..16c15e7 --- /dev/null +++ b/dlogutil/src/lib.rs @@ -0,0 +1,823 @@ +use dlog::{BufferId, Priority}; +use dlog_sys::{log_id_t, log_priority}; +use dlogutil_sys::*; +use libc::timespec; +use std::ffi::CStr; +use std::io::{Error, ErrorKind, Result}; +use std::ptr::{null, null_mut}; +use std::time::Duration; + +/// Enumeration for timestamp-based log sorting orderings. +/// +/// For more detailed information on the timestamp documentation, +/// refer to the C library documentation. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum SortingOrder { + /// Monotonic timestamp applied by the sender. + SentMono, + /// Real-time timestamp applied by the sender. + SentReal, + /// Monotonic timestamp applied by the receiver. + RecvMono, + /// Real-time timestamp applied by the receiver. + RecvReal, + /// The default timestamp of the buffer. See [`buffer_get_default_ts_type`]. + Default, +} + +impl From for SortingOrder { + fn from(c_enum: dlogutil_sorting_order) -> Self { + match c_enum { + dlogutil_sorting_order::DLOGUTIL_SORT_SENT_MONO => SortingOrder::SentMono, + dlogutil_sorting_order::DLOGUTIL_SORT_SENT_REAL => SortingOrder::SentReal, + dlogutil_sorting_order::DLOGUTIL_SORT_RECV_MONO => SortingOrder::RecvMono, + dlogutil_sorting_order::DLOGUTIL_SORT_RECV_REAL => SortingOrder::RecvReal, + dlogutil_sorting_order::DLOGUTIL_SORT_DEFAULT => SortingOrder::Default, + } + } +} + +impl From for dlogutil_sorting_order { + fn from(rust_enum: SortingOrder) -> Self { + match rust_enum { + SortingOrder::SentMono => dlogutil_sorting_order::DLOGUTIL_SORT_SENT_MONO, + SortingOrder::SentReal => dlogutil_sorting_order::DLOGUTIL_SORT_SENT_REAL, + SortingOrder::RecvMono => dlogutil_sorting_order::DLOGUTIL_SORT_RECV_MONO, + SortingOrder::RecvReal => dlogutil_sorting_order::DLOGUTIL_SORT_RECV_REAL, + SortingOrder::Default => dlogutil_sorting_order::DLOGUTIL_SORT_DEFAULT, + } + } +} + +/// A struct containing libdlogutil initialisation configuration. +pub struct DlogutilConfig { + config: *mut dlogutil_config, +} + +// SAFETY: it is ok to use the this object from different threads if only one thread +// is using the memory at the time. +unsafe impl Send for DlogutilConfig {} + +// SAFETY: the operations that do not modify the memory at all are ok to be used from +// different threads at once, and those who do are marked as &mut. +unsafe impl Sync for DlogutilConfig {} + +/// A struct containing the state of a log handling request. +pub struct DlogutilState { + state: *mut dlogutil_state, +} + +// SAFETY: it is ok to use the this object from different threads if only one thread +// is using the memory at the time. +unsafe impl Send for DlogutilState {} + +// SAFETY: the operations that do not modify the memory at all are ok to be used from +// different threads at once, and those who do are marked as &mut. +unsafe impl Sync for DlogutilState {} + +/// A struct containing the metadata and contents for a single dlog entry. +pub struct DlogutilEntry { + entry: *mut dlogutil_entry, +} + +// SAFETY: it is ok to use the this object from different threads if only one thread +// is using the memory at the time. +unsafe impl Send for DlogutilEntry {} + +// SAFETY: the operations that do not modify the memory at all are ok to be used from +// different threads at once, and those who do are marked as &mut. +unsafe impl Sync for DlogutilEntry {} + +impl Drop for DlogutilConfig { + fn drop(&mut self) { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + unsafe { + dlogutil_config_destroy(self.config); + } + } +} + +impl Drop for DlogutilState { + fn drop(&mut self) { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + unsafe { + dlogutil_state_destroy(self.state); + } + } +} + +impl Drop for DlogutilEntry { + fn drop(&mut self) { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + unsafe { + dlogutil_entry_destroy(self.entry); + } + } +} + +impl DlogutilConfig { + /// Creates a new [`DlogutilConfig`] struct to be filled with configuration. + /// + /// Useful for dumping the logs of a specific thread in a multithreaded process. + /// + /// # Returns + /// + /// * `Ok(Self)` - A handle to the config struct. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory. + pub fn create() -> Result { + // SAFETY: the function is safe by itself. It returns either a valid pointer, + // in which case we return Ok, or NULL, in which case we return Err, which + // means that if Self is created, the inner pointer is guaranteed to be correct. + let config = unsafe { dlogutil_config_create() }; + if config.is_null() { + return Err(Error::from(ErrorKind::OutOfMemory)); + } + Ok(DlogutilConfig { config }) + } + + /// Enables retrieving only those logs that are logged by the thread with + /// the given TID. + /// + /// Useful for dumping the logs of a specific thread in a multithreaded process. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory. + pub fn filter_tid(&mut self, tid: i32) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_filter_tid(self.config, tid) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Enables retrieving only those logs that are logged by the process with + /// the given PID. + /// + /// Useful for dumping the logs of a specific process. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory. + pub fn filter_pid(&mut self, pid: i32) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_filter_pid(self.config, pid) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Enables retrieving only those logs that match a given filter. + /// + /// # Arguments + /// + /// * `query` - The filter query. For syntax, see dlogutil's --help. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - Invalid syntax of the filterspec. + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + /// * [`std::io::ErrorKind::OutOfMemory`] - Out of memory. + pub fn filter_filterspec(&mut self, query: &str) -> Result<()> { + let query = format!("{}\0", query); + let query_c = + CStr::from_bytes_with_nul(query.as_bytes()).map_err(|_| ErrorKind::InvalidInput)?; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // The string is defined in a verified way above. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_filter_filterspec(self.config, query_c.as_ptr()) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Disables log sorting for given log retrieval request. + /// + /// Logs are still received in some order that is usually largely sorted, + /// but if sorting is disabled logutil-side there may be cases where a log + /// with a later timestamp is in front of a log with an earlier one. + /// The use case here is performance, since the failure case above is rare. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn sorting_disable(&mut self) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_sorting_disable(self.config) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Enables log sorting for given log retrieval request. + /// + /// This is the default and generally makes sure that logs are in order, + /// but has a modest performance cost. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn sorting_enable(&mut self) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_sorting_enable(self.config) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Enables sorting, choosing the sort buffer size manually. + /// + /// The count parameter influences the quality of sorting, but also memory usage. + /// This version is a somewhat lower level version of [`sorting_enable`]. + /// For more information on sorting quality, refer to the C library documentation. + /// + /// [`sorting_enable`]: DlogutilConfig::sorting_enable + /// + /// # Arguments + /// + /// * `entry_count` - How many logs to keep at a given time. At least 1. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - Zero size. + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn sorting_enable_with_size(&mut self, entry_count: u32) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_sorting_enable_with_size(self.config, entry_count) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Chooses a timestamp type by which returned logs are sorted by. + /// + /// If the chosen timestamp is missing in the logs, currently they will not + /// be sorted at all. This should, however, still become a reasonable order, + /// since logs are usually stored sorted by one of timestamps. If only some + /// logs are missing the chosen timestamp, ordering of the logs is undefined. + /// See [`buffer_get_default_ts_type`] for the default. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn order_set(&mut self, sort_by: SortingOrder) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_order_set(self.config, sort_by.into()) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Adds a buffer whence logs will be taken to a request. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn buffer_add(&mut self, buf: BufferId) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_buffer_add(self.config, buf.into()) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Set log retrieval mode to retrieving all the logs since the start of + /// the system without an end. + /// + /// This is similar to `dlogutil` in a default mode. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn mode_set_continuous(&mut self) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_mode_set_continuous(self.config) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Set log retrieval mode to retrieving all the logs since the call + /// without an end. + /// + /// This is similar to `dlogutil -m`. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn mode_set_monitor(&mut self) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_mode_set_monitor(self.config) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Set log retrieval mode to dumping all the logs since the start of the + /// system until the call (possibly a specified amount of the most recent + /// of them instead). + /// + /// This is similar to `dlogutil -d`. After dumping all the logs, + /// [`get_log`] will signal this by returning `TIZEN_ERROR_NO_DATA`. + /// + /// [`get_log`]: DlogutilState::get_log + /// + /// # Arguments + /// + /// * `entry_count` - Number of logs to be returned. It can be + /// `DLOGUTIL_MAX_DUMP_SIZE`, in which case all the logs will be dumped. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn mode_set_dump(&mut self, entry_count: u32) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_config_mode_set_dump(self.config, entry_count) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Set log retrieval mode to dumping compressed historical logs. + /// + /// This is similar to `cat /var/log/dlog/xyz`. After dumping all the logs, + /// [`get_log`] will signal this by returning `TIZEN_ERROR_NO_DATA`. + /// + /// [`get_log`]: DlogutilState::get_log + /// + /// # Arguments + /// + /// * `compress_buffer` - The name of the compression storage entry. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::OutOfMemory`] - Not enough memory. Parameters left unchanged. + /// * [`std::io::ErrorKind::InvalidInput`] - The pointer was NULL. + pub fn mode_set_compressed_memory_dump(&mut self, compress_buffer: &str) -> Result<()> { + let compress_buffer = format!("{}\0", compress_buffer); + let compress_buffer_c = CStr::from_bytes_with_nul(compress_buffer.as_bytes()) + .map_err(|_| ErrorKind::InvalidInput)?; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // The string is defined in a verified way above. + // Since the function takes &mut, no other references can exist. + match unsafe { + dlogutil_config_mode_set_compressed_memory_dump(self.config, compress_buffer_c.as_ptr()) + } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } +} + +impl DlogutilState { + /// Finalizes the config into a state struct by connecting to buffers. + /// + /// An application having platform privilege level can read platform log + /// data by declaring . + /// + /// # Returns + /// + /// * `Ok(Self)` - A handle to the state struct. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::InvalidInput`] - No buffers selected. + /// * [`std::io::ErrorKind::Uncategorized`] - Unsupported buffer set (KMSG + non-KMSG). + /// * [`std::io::ErrorKind::Uncategorized`] - Unsupported backend (zero-copy). + /// * [`std::io::ErrorKind::Uncategorized`] - No buffers were opened (incl. due to null backend). + /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't read config file. + /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't contact log backend. + /// * [`std::io::ErrorKind::OutOfMemory`] - There's not enough memory. + pub fn create(config: DlogutilConfig) -> Result { + let mut state = DlogutilState { state: null_mut() }; + // SAFETY: the config pointer is guaranteed to be correct by the way it's created. + // Otherwise, the function is safe by itself. It returns either a valid pointer, + // in which case we return Ok, or NULL, in which case we return Err, which + // means that if Self is created, the inner pointer is guaranteed to be correct. + match unsafe { dlogutil_config_connect(config.config, &mut state.state) } { + 0 => Ok(state), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Retrieves a single log according to a dump request. + /// + /// Returns a returned log as a [`DlogutilEntry`] struct. + /// + /// If the calling process doesn't have `CAP_SYSLOG` and is not in the + /// log group, you will only get some of the logs. + /// Also, you must set the mode (`DlogutilConfig::mode_set_*`). + /// + /// [`DlogutilEntry`]: DlogutilEntry + /// + /// # Arguments + /// + /// * `timeout` - How many miliseconds to wait for the log. + /// The actual runtime of the call can obviously be slightly longer than + /// this argument. 0 means don't wait, None means wait indefinitely. + /// + /// # Returns + /// + /// * `Ok(DlogutilEntry)` - A returned log. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::Uncategorized`] - Timeout exceeded. + /// * [`std::io::ErrorKind::Uncategorized`] - In dump mode, no more logs remaining. + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::InvalidInput`] - State not in log-getting mode. + /// * [`std::io::ErrorKind::OutOfMemory`] - There's not enough memory. + /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request. + pub fn get_log(&mut self, timeout: Option) -> Result { + let mut entry_out = DlogutilEntry { entry: null_mut() }; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // The C function passes the ownership of the entry to the pointer iff the + // return value is nonzero, which is exactly the case in which we return + // the struct in an Ok. Since the function takes &mut, no other references can exist. + match unsafe { + dlogutil_get_log( + self.state, + timeout.map_or(-1, |t| t.as_millis() as i32), + &mut entry_out.entry, + ) + } { + 0 => Ok(entry_out), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Irreversibly clears a log buffer from any logs inside. + /// + /// Either `CAP_SYSLOG` or being in the log group is required. + /// Also, you can't use one of the log-getting modes + /// (`DlogutilConfig::mode_set_*`). + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request. + pub fn buffer_clear(&mut self, buffer: BufferId) -> Result<()> { + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Since the function takes &mut, no other references can exist. + match unsafe { dlogutil_buffer_clear(self.state, buffer.into()) } { + 0 => Ok(()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Gets the data storage capacity of a log buffer in bytes. + /// + /// Either `CAP_SYSLOG` or being in the log group is required. + /// Also, you can't use one of the log-getting modes + /// (`DlogutilConfig::mode_set_*`). + /// + /// # Returns + /// + /// * `Ok(u32)` - The buffer's maximum capacity in bytes. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - State in log-getting mode. + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request. + pub fn buffer_get_capacity(&mut self, buffer: BufferId) -> Result { + let mut capacity = 0; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Note that it is not correct for this function to take a shared reference, as in case of + // the pipe backend, the function will write to a pipe and expect to read an answer + // immediately. + match unsafe { dlogutil_buffer_get_capacity(self.state, buffer.into(), &mut capacity) } { + 0 => Ok(capacity), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Gets the storage data usage of a log buffer in bytes. + /// + /// Either `CAP_SYSLOG` or being in the log group is required. + /// Also, you can't use one of the log-getting modes + /// (`DlogutilConfig::mode_set_*`). + /// + /// # Returns + /// + /// * `Ok(u32)` - Buffer's current usage in bytes. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - State in log-getting mode. + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - Couldn't fulfill request. + pub fn buffer_get_usage(&mut self, buffer: BufferId) -> Result { + let mut usage = 0; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // Note that it is not correct for this function to take a shared reference, as in case of + // the pipe backend, the function will write to a pipe and expect to read an answer + // immediately. + match unsafe { dlogutil_buffer_get_usage(self.state, buffer.into(), &mut usage) } { + 0 => Ok(usage), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Gets the buffer aliasing (same storage) information. + /// + /// Sometimes, multiple buffers will be backed by a single log storage + /// (for example, by the same kernel device). In such cases, the storage + /// will only be opened once. + /// This function allows you to see whether this is the case. + /// + /// # Returns + /// + /// * `Ok(BufferId)` - Buffer aliasing information. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + pub fn buffer_get_alias(&self, buffer: BufferId) -> Result { + let mut real_buffer = log_id_t::LOG_ID_MAIN; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // This method only reads from the memory of the object, so it is ok for it to take + // a shared reference. + match unsafe { dlogutil_buffer_get_alias(self.state, buffer.into(), &mut real_buffer) } { + 0 => Ok(real_buffer.into()), + e => Err(Error::from_raw_os_error(-e)), + } + } +} + +impl DlogutilEntry { + /// Retrieves the timestamp of given type from the log entry. + /// + /// The information about timestamp availability can be retrieved using the + /// [`buffer_check_ts_type_available`] function. + /// + /// # Returns + /// + /// * `Ok(Duration)` - Timestamp of the entry as a [`Duration`] struct. + /// * `Err(std::io::Error)` - An error. + /// + /// [`Duration`]: std::time::Duration + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - Invalid value of order. + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - The timestamp is missing. + pub fn get_timestamp(&self, stamp_type: SortingOrder) -> Result { + let mut ts = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // This method only reads from the memory of the object, so it is ok for it to take + // a shared reference. + match unsafe { dlogutil_entry_get_timestamp(self.entry, stamp_type.into(), &mut ts) } { + 0 => Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Retrieves the TID (thread identificator) of the log sender. + /// + /// If [`LogIdKmsg`] is used as the buffer, this function will always return + /// `TIZEN_ERROR_NO_DATA`. + /// This is because the KMSG buffer contains no TID information. + /// + /// [`LogIdKmsg`]: BufferId::LogIdKmsg + /// + /// # Returns + /// + /// * `Ok(i32)` - TID of the log sender. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - TID is missing or not applicable. + pub fn get_tid(&self) -> Result { + let mut tid = 0; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // This method only reads from the memory of the object, so it is ok for it to take + // a shared reference. + match unsafe { dlogutil_entry_get_tid(self.entry, &mut tid) } { + 0 => Ok(tid), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Retrieves the PID (process identificator) of the log sender. + /// + /// If [`LogIdKmsg`] is used as the buffer, this function will always return + /// `TIZEN_ERROR_NO_DATA`. + /// This is because the KMSG buffer contains no PID information. + /// + /// [`LogIdKmsg`]: BufferId::LogIdKmsg + /// + /// # Returns + /// + /// * `Ok(i32)` - PID of the log sender. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - PID is missing or not applicable. + pub fn get_pid(&self) -> Result { + let mut pid = 0; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // This method only reads from the memory of the object, so it is ok for it to take + // a shared reference. + match unsafe { dlogutil_entry_get_pid(self.entry, &mut pid) } { + 0 => Ok(pid), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Retrieves the priority level metadata of the log entry. + /// + /// # Returns + /// + /// * `Ok(Priority)` - Log priority. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - The priority is missing. + pub fn get_priority(&self) -> Result { + let mut prio = log_priority::DLOG_UNKNOWN; + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // This method only reads from the memory of the object, so it is ok for it to take + // a shared reference. + match unsafe { dlogutil_entry_get_priority(self.entry, &mut prio) } { + 0 => Ok(prio.into()), + e => Err(Error::from_raw_os_error(-e)), + } + } + + /// Retrieves the tag (arbitrary label) of the log entry. + /// + /// In some rare cases the entry may be malformed and the tag + /// may turn out to be unavailable. + /// + /// In such cases, an empty string may be returned instead. + /// + /// # Returns + /// + /// * `Ok(&str)` - Log tag. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - The tag is missing. + pub fn get_tag(&self) -> Result<&str> { + let mut tag_ptr = null(); + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // The function returns either NULL, in which case we return Err, + // or a string with same lifetime as self.entry, in which case we can safely + // call CStr::from_ptr and return it in an Ok. + // This method only reads from the memory of the object, so it is ok for it to take + // a shared reference. + unsafe { + match dlogutil_entry_get_tag(self.entry, &mut tag_ptr) { + 0 => Ok(CStr::from_ptr(tag_ptr).to_str().unwrap()), + e => Err(Error::from_raw_os_error(-e)), + } + } + } + + /// Retrieves the message (without any metadata) of the log entry. + /// + /// In some rare cases the entry may be malformed and the message + /// may turn out to be unavailable. + /// + /// In such cases, an empty string may be returned instead. + /// + /// # Returns + /// + /// * `Ok(&str)` - Log message. + /// * `Err(std::io::Error)` - An error. + /// + /// # Errors + /// + /// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. + /// * [`std::io::ErrorKind::Uncategorized`] - The message is missing. + pub fn get_message(&self) -> Result<&str> { + let mut msg_ptr = null(); + // SAFETY: the pointer is guaranteed to be correct by the way it's created. + // The function returns either NULL, in which case we return Err, + // or a string with same lifetime as self.entry, in which case we can safely + // call CStr::from_ptr and return it in an Ok. + // This method only reads from the memory of the object, so it is ok for it to take + // a shared reference. + unsafe { + match dlogutil_entry_get_message(self.entry, &mut msg_ptr) { + 0 => Ok(CStr::from_ptr(msg_ptr).to_str().unwrap()), + e => Err(Error::from_raw_os_error(-e)), + } + } + } +} + +/// Gets the human-readable, constant name of a buffer. +/// +/// # Returns +/// +/// * `Ok(&str)` - The name of the passed buffer. +/// * `Err(std::io::Error)` - An error. +/// +/// # Errors +/// +/// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. +pub fn buffer_get_name(buffer: BufferId) -> Result<&'static str> { + let mut name_ptr = null(); + // SAFETY: the function is safe by itself provided the target is a valid pointer. + // The function returns either NULL, in which case we return Err, + // or a string with static lifetime, in which case we can safely call + // CStr::from_ptr and return it in an Ok. + unsafe { + match dlogutil_buffer_get_name(buffer.into(), &mut name_ptr) { + 0 => Ok(CStr::from_ptr(name_ptr).to_str().unwrap()), + e => Err(Error::from_raw_os_error(-e)), + } + } +} + +/// Gets the default sorting timestamp type of a buffer. +/// +/// This is the timestamp type that will be used for sorting by default. +/// We assume that it is always available and the input is sorted by it. +/// +/// # Returns +/// +/// * `Ok(SortingOrder)` - The default timestamp type of the passed buffer. +/// * `Err(std::io::Error)` - An error. +/// +/// # Errors +/// +/// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. +/// * [`std::io::ErrorKind::Uncategorized`] - Couldn't read config file. +pub fn buffer_get_default_ts_type(buffer: BufferId) -> Result { + let mut typ = dlogutil_sorting_order::DLOGUTIL_SORT_SENT_MONO; + // SAFETY: the function is safe by itself provided the target is a valid pointer. + match unsafe { dlogutil_buffer_get_default_ts_type(buffer.into(), &mut typ) } { + 0 => Ok(typ.into()), + e => Err(Error::from_raw_os_error(-e)), + } +} + +/// Checks if a buffer contains timestamps of a given type. +/// +/// If false is returned, the timestamp may still be available in some of the logs. +/// However, if true is returned, the timestamp will always be available. +/// You can check the timestamp availability per log using the [`get_timestamp`] +/// function. +/// +/// [`get_timestamp`]: DlogutilEntry::get_timestamp +/// +/// # Returns +/// +/// * `Ok(bool)` - Whether the given timestamp type is guaranteed to be available. +/// * `Err(std::io::Error)` - An error. +/// +/// # Errors +/// +/// * [`std::io::ErrorKind::InvalidInput`] - More than one buffer. +/// * [`std::io::ErrorKind::InvalidInput`] - One of the pointers was NULL. +/// * [`std::io::ErrorKind::Uncategorized`] - Couldn't read config file. +pub fn buffer_check_ts_type_available(buffer: BufferId, stamp_type: SortingOrder) -> Result { + let mut available = false; + // SAFETY: the function is safe by itself provided the target is a valid pointer. + match unsafe { + dlogutil_buffer_check_ts_type_available(buffer.into(), stamp_type.into(), &mut available) + } { + 0 => Ok(available), + e => Err(Error::from_raw_os_error(-e)), + } +} diff --git a/packaging/rust-dlog.manifest b/packaging/rust-dlog.manifest new file mode 100644 index 0000000..1a63101 --- /dev/null +++ b/packaging/rust-dlog.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/packaging/rust-dlog.spec b/packaging/rust-dlog.spec new file mode 100644 index 0000000..64f1f41 --- /dev/null +++ b/packaging/rust-dlog.spec @@ -0,0 +1,216 @@ +%define _rpm_strip_option --keep-section=.rustc + +%global rustc_edition 2021 + +Name: rust-dlog +Summary: rust-dlog +Version: 0.1 +Release: 1 +Group: System/Management +License: MIT +Source0: %{name}-%{version}.tar.gz +Source1: %{name}.manifest +BuildRequires: rust +BuildRequires: rust-libc +BuildRequires: rust-log +BuildRequires: libdlog-devel + +%description +dlog rust bindings + +%package -n rust-libdlog +Summary: Logging service dlog API +%description -n rust-libdlog + +%package -n rust-libdlogutil +Summary: Logging service dlog API +%description -n rust-libdlogutil + +%package tests +Summary: Rust-dlog tests +Requires: rust-devel +%description tests + +%package doc +Summary: Rust-dlog documentation +%description doc + +%prep +%setup -q +cp %{SOURCE1} . + +%build +build_library () { + %{rustc_std_build} \ + --crate-type=dylib \ + $@ + clippy-driver \ + -C prefer-dynamic \ + --extern std=${__rust_std} \ + -L%{_rust_libdir} \ + -L%{_rust_dylibdir} \ + --crate-type=dylib \ + --out-dir=/tmp \ + -Dclippy::undocumented_unsafe_blocks \ + $@ + rustdoc \ + -C prefer-dynamic \ + --extern std=${__rust_std} \ + -L%{_rust_libdir} \ + -L%{_rust_dylibdir} \ + --crate-type=dylib \ + -o rustdoc_output \ + $@ +} + +build_library \ + --crate-name=dlog_sys \ + --edition=%{rustc_edition} \ + --cfg='feature="std"' \ + ./dlog-sys/src/lib.rs + +build_library \ + --crate-name=dlog \ + --edition=%{rustc_edition} \ + --cfg='feature="std"' \ + %rust_dylib_extern log \ + --extern dlog_sys=libdlog_sys.so \ + ./dlog/src/lib.rs + +build_library \ + --crate-name=dlogutil_sys \ + --edition=%{rustc_edition} \ + --cfg='feature="std"' \ + %rust_dylib_extern libc \ + --extern dlog_sys=libdlog_sys.so \ + ./dlogutil-sys/src/lib.rs + +build_library \ + --crate-name=dlogutil \ + --edition=%{rustc_edition} \ + --cfg='feature="std"' \ + -L. \ + %rust_dylib_extern libc \ + --extern dlog_sys=libdlog_sys.so \ + --extern dlog=libdlog.so \ + --extern dlogutil_sys=libdlogutil_sys.so \ + ./dlogutil/src/lib.rs + +build_library \ + --crate-name=dlog_redirect_stdout_sys \ + --edition=%{rustc_edition} \ + --cfg='feature="std"' \ + ./dlog-redirect-stdout-sys/src/lib.rs + +build_library \ + --crate-name=dlog_redirect_stdout \ + --edition=%{rustc_edition} \ + --cfg='feature="std"' \ + -L. \ + --extern dlog=libdlog.so \ + --extern dlog_redirect_stdout_sys=libdlog_redirect_stdout_sys.so \ + ./dlog-redirect-stdout/src/lib.rs + +%{rustc_std_build} --crate-type=bin \ + --crate-name=dlogsend_rust \ + --edition=%{rustc_edition} \ + --cfg='feature="std"' \ + -L. \ + %rust_dylib_extern log \ + --extern dlog=libdlog.so \ + --extern dlogutil=libdlogutil.so \ + -Clink-arg=%{_libdir}/libdlog.so \ + ./tests/src/dlogsend.rs + +TESTFLAGS="--edition=%{rustc_edition} \ + --cfg feature=\"std\" \ + -L. \ + %rust_dylib_extern libc \ + %rust_dylib_extern log \ + --extern dlog=libdlog.so \ + --extern dlogutil=libdlogutil.so \ + --extern dlog_redirect_stdout=libdlog_redirect_stdout.so \ + -Clink-arg=%{_libdir}/libdlog.so \ + -Clink-arg=%{_libdir}/libdlogutil.so \ + -Clink-arg=%{_libdir}/libdlog_redirect_stdout.so \ + " + +build_test () { + %{rustc_std_build} \ + --crate-type=bin \ + $TESTFLAGS \ + $@ + clippy-driver \ + -C prefer-dynamic \ + --extern std=${__rust_std} \ + -L%{_rust_libdir} \ + -L%{_rust_dylibdir} \ + --crate-type=bin \ + --out-dir=/tmp \ + -Dclippy::undocumented_unsafe_blocks \ + $TESTFLAGS \ + $@ +} + +build_test \ + --crate-name=test_dlog_print \ + ./tests/src/test_dlog_print.rs + +build_test \ + --crate-name=test_dlog_critical_print \ + ./tests/src/test_dlog_critical_print.rs + +build_test \ + --crate-name=test_dlog_custom_tag \ + ./tests/src/test_dlog_custom_tag.rs + +build_test \ + --crate-name=test_dlog_redirect \ + ./tests/src/test_dlog_redirect.rs + +%install +mkdir -p %{buildroot}%{_bindir} + +install -d -m 0755 %{buildroot}%{_rust_dylibdir} +install -m 0644 libdlog_sys.so %{buildroot}%{_rust_dylibdir}/libdlog_sys.so +install -m 0644 libdlog.so %{buildroot}%{_rust_dylibdir}/libdlog.so +install -m 0644 libdlogutil_sys.so %{buildroot}%{_rust_dylibdir}/libdlogutil_sys.so +install -m 0644 libdlogutil.so %{buildroot}%{_rust_dylibdir}/libdlogutil.so +install -m 0644 libdlog_redirect_stdout_sys.so %{buildroot}%{_rust_dylibdir}/libdlog_redirect_stdout_sys.so +install -m 0644 libdlog_redirect_stdout.so %{buildroot}%{_rust_dylibdir}/libdlog_redirect_stdout.so +install -m 0755 dlogsend_rust %{buildroot}%{_bindir}/dlogsend_rust + +for file in test_*; do + install -m 0755 "$file" %{buildroot}%{_bindir}/"$file" +done + +mkdir -p %{buildroot}/usr/share/doc/rust-dlog +cp -r rustdoc_output/* %{buildroot}/usr/share/doc/rust-dlog + +%files +%manifest %{name}.manifest + +%files -n rust-libdlog +%manifest %{name}.manifest +%{_rust_dylibdir}/libdlog_sys.so +%{_rust_dylibdir}/libdlog.so +%{_rust_dylibdir}/libdlog_redirect_stdout_sys.so +%{_rust_dylibdir}/libdlog_redirect_stdout.so + +%files -n rust-libdlogutil +%manifest %{name}.manifest +%{_rust_dylibdir}/libdlogutil_sys.so +%{_rust_dylibdir}/libdlogutil.so + +%files tests +%manifest %{name}.manifest +%attr(750,log,log) %{_bindir}/dlogsend_rust +%attr(750,log,log) %{_bindir}/test_* + +%post tests +echo "This package contains binaries: dlogsend_rust, which should be treated as an example," +echo "and integration tests that are run in dlog_test with other dlog tests." + +%files doc +%manifest %{name}.manifest +/usr/share/doc/rust-dlog diff --git a/tests/src/dlogsend.rs b/tests/src/dlogsend.rs new file mode 100644 index 0000000..8e05b0a --- /dev/null +++ b/tests/src/dlogsend.rs @@ -0,0 +1,22 @@ +pub mod logutil; + +use logutil::{log_message, parse_options}; +use std::io; +use std::io::{BufRead, Result}; + +fn main() -> Result<()> { + let opts = parse_options()?; + + dlog::init(opts.is_critical)?; + + if opts.msg.is_empty() { + let stdin = io::stdin(); + for line in stdin.lock().lines() { + log_message(opts.prio, &line.unwrap()); + } + } else { + log_message(opts.prio, &opts.msg); + } + + Ok(()) +} diff --git a/tests/src/logutil.rs b/tests/src/logutil.rs new file mode 100644 index 0000000..2ed9fa1 --- /dev/null +++ b/tests/src/logutil.rs @@ -0,0 +1,96 @@ +use dlog::{BufferId, Priority}; +use dlogutil::{DlogutilConfig, DlogutilState}; +use log::{debug, error, info, trace, warn}; +use std::env; +use std::io::{Error, Result}; + +pub struct Options { + pub prio: Priority, + pub is_critical: bool, + pub msg: String, +} + +fn str_to_prio(prog: &str, s: &str) -> Result { + match s { + "1" | "*" => Ok(Priority::Default), + "2" | "v" => Ok(Priority::Verbose), + "3" | "d" => Ok(Priority::Debug), + "4" | "i" => Ok(Priority::Info), + "5" | "w" => Ok(Priority::Warn), + "6" | "e" => Ok(Priority::Error), + "7" | "f" => Ok(Priority::Fatal), + _ => { + show_help(prog); + Err(Error::from_raw_os_error(22)) + } + } +} + +pub fn parse_options() -> Result { + let args: Vec = env::args().collect(); + let mut opts = Options { + prio: Priority::Info, + is_critical: false, + msg: String::new(), + }; + + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "-p" => { + if i + 1 < args.len() { + opts.prio = str_to_prio(&args[0], &args[i + 1])?; + i += 1; + } + } + "-k" => { + opts.is_critical = true; + } + "-h" => { + show_help(&args[0]); + return Err(Error::from_raw_os_error(22)); + } + _ => { + opts.msg = args[i..].join(" "); + break; + } + } + i += 1; + } + + Ok(opts) +} + +fn show_help(prog: &str) { + println!( + "\ +Usage: {prog} [-p priority] [-k] [message] +\t-p priority \tone of {{Debug, Info, Warning, Error, Verbose, Fatal}} +\t \tfirst letter is enough; defaults to Info +\t-k \tmake this a 'critical' log +\t-h \tshow this help +\tmessage \tmessage for logging; if not specified, each line from standard input +\t \tis logged separately until EOF" + ); +} + +pub fn log_message(prio: Priority, msg: &str) { + match prio { + Priority::Default => info!("{}", msg), + Priority::Verbose => trace!("{}", msg), + Priority::Debug => debug!("{}", msg), + Priority::Info => info!("{}", msg), + Priority::Warn => warn!("{}", msg), + Priority::Error => error!("{}", msg), + Priority::Fatal => error!("{}", msg), + _ => panic!("impossible case"), + } +} + +pub fn clear_buffer(buffer: BufferId) -> Result<()> { + let mut config = DlogutilConfig::create()?; + config.buffer_add(buffer)?; + let mut state = DlogutilState::create(config)?; + state.buffer_clear(buffer)?; + Ok(()) +} diff --git a/tests/src/test_dlog_critical_print.rs b/tests/src/test_dlog_critical_print.rs new file mode 100644 index 0000000..58c910d --- /dev/null +++ b/tests/src/test_dlog_critical_print.rs @@ -0,0 +1,58 @@ +pub mod logutil; + +use dlog::{BufferId, Priority}; +use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder}; +use log::{debug, error, info, trace, warn}; +use logutil::clear_buffer; +use std::io::Result; +use std::time::Duration; + +fn test_dlog_critical_print() -> Result<()> { + let mut config = DlogutilConfig::create()?; + config.filter_filterspec("test_dlog_critical_print")?; + config.buffer_add(BufferId::LogIdMain)?; + config.mode_set_continuous()?; + config.order_set(SortingOrder::Default)?; + config.sorting_enable()?; + let mut state = DlogutilState::create(config)?; + + dlog::init(true)?; + + trace!("test_verbose_crit"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Verbose); + assert_eq!(entry.get_tag()?, "test_dlog_critical_print"); + assert!(entry.get_message()?.contains("test_verbose_crit")); + + debug!("test_debug_crit"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Debug); + assert_eq!(entry.get_tag()?, "test_dlog_critical_print"); + assert!(entry.get_message()?.contains("test_debug_crit")); + + info!("test_info_crit"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Info); + assert_eq!(entry.get_tag()?, "test_dlog_critical_print"); + assert!(entry.get_message()?.contains("test_info_crit")); + + warn!("test_warn_crit"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Warn); + assert_eq!(entry.get_tag()?, "test_dlog_critical_print"); + assert!(entry.get_message()?.contains("test_warn_crit")); + + error!("test_error_crit"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Error); + assert_eq!(entry.get_tag()?, "test_dlog_critical_print"); + assert!(entry.get_message()?.contains("test_error_crit")); + + clear_buffer(BufferId::LogIdMain)?; + + Ok(()) +} + +fn main() -> Result<()> { + test_dlog_critical_print() +} diff --git a/tests/src/test_dlog_custom_tag.rs b/tests/src/test_dlog_custom_tag.rs new file mode 100644 index 0000000..8417fa3 --- /dev/null +++ b/tests/src/test_dlog_custom_tag.rs @@ -0,0 +1,36 @@ +pub mod logutil; + +use dlog::BufferId; +use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder}; +use logutil::clear_buffer; +use std::io::Result; +use std::time::Duration; + +fn test_custom_tag() -> Result<()> { + let mut config = DlogutilConfig::create()?; + config.filter_filterspec("test_dlog_custom_tag")?; + config.filter_filterspec("custom_tag")?; + config.buffer_add(BufferId::LogIdApps)?; + config.mode_set_continuous()?; + config.order_set(SortingOrder::Default)?; + config.sorting_enable()?; + let mut state = DlogutilState::create(config)?; + + dlog::init(false)?; + + log::info!("log with the default tag"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_tag()?, "test_dlog_custom_tag"); + + log::info!(target: "custom_tag", "log with a custom tag"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_tag()?, "custom_tag"); + + clear_buffer(BufferId::LogIdApps)?; + + Ok(()) +} + +fn main() -> Result<()> { + test_custom_tag() +} diff --git a/tests/src/test_dlog_print.rs b/tests/src/test_dlog_print.rs new file mode 100644 index 0000000..5463990 --- /dev/null +++ b/tests/src/test_dlog_print.rs @@ -0,0 +1,58 @@ +pub mod logutil; + +use dlog::{BufferId, Priority}; +use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder}; +use log::{debug, error, info, trace, warn}; +use logutil::clear_buffer; +use std::io::Result; +use std::time::Duration; + +fn test_dlog_print() -> Result<()> { + let mut config = DlogutilConfig::create()?; + config.filter_filterspec("test_dlog_print")?; + config.buffer_add(BufferId::LogIdApps)?; + config.mode_set_continuous()?; + config.order_set(SortingOrder::Default)?; + config.sorting_enable()?; + let mut state = DlogutilState::create(config)?; + + dlog::init(false)?; + + trace!("test_verbose"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Verbose); + assert_eq!(entry.get_tag()?, "test_dlog_print"); + assert!(entry.get_message()?.contains("test_verbose")); + + debug!("test_debug"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Debug); + assert_eq!(entry.get_tag()?, "test_dlog_print"); + assert!(entry.get_message()?.contains("test_debug")); + + info!("test_info"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Info); + assert_eq!(entry.get_tag()?, "test_dlog_print"); + assert!(entry.get_message()?.contains("test_info")); + + warn!("test_warn"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Warn); + assert_eq!(entry.get_tag()?, "test_dlog_print"); + assert!(entry.get_message()?.contains("test_warn")); + + error!("test_error"); + let entry = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry.get_priority()?, Priority::Error); + assert_eq!(entry.get_tag()?, "test_dlog_print"); + assert!(entry.get_message()?.contains("test_error")); + + clear_buffer(BufferId::LogIdApps)?; + + Ok(()) +} + +fn main() -> Result<()> { + test_dlog_print() +} diff --git a/tests/src/test_dlog_redirect.rs b/tests/src/test_dlog_redirect.rs new file mode 100644 index 0000000..01c76de --- /dev/null +++ b/tests/src/test_dlog_redirect.rs @@ -0,0 +1,41 @@ +pub mod logutil; + +use dlog::{BufferId, Priority}; +use dlog_redirect_stdout::{redirect_stderr, redirect_stdout}; +use dlogutil::{DlogutilConfig, DlogutilState, SortingOrder}; +use logutil::clear_buffer; +use std::io::Result; +use std::time::Duration; + +fn test_dlog_redirect() -> Result<()> { + let mut config = DlogutilConfig::create()?; + config.filter_filterspec("dlog_test_redirect")?; + config.buffer_add(BufferId::LogIdMain)?; + config.mode_set_continuous()?; + config.order_set(SortingOrder::Default)?; + config.sorting_enable()?; + let mut state = DlogutilState::create(config)?; + + redirect_stdout(BufferId::LogIdMain, "dlog_test_redirect", Priority::Info)?; + redirect_stderr(BufferId::LogIdMain, "dlog_test_redirect", Priority::Error)?; + + println!("test_redirect_stdout"); + let entry_out = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry_out.get_priority()?, Priority::Info); + assert_eq!(entry_out.get_tag()?, "dlog_test_redirect"); + assert_eq!(entry_out.get_message()?, "test_redirect_stdout"); + + eprintln!("test_redirect_stderr"); + let entry_err = state.get_log(Some(Duration::from_millis(1_000)))?; + assert_eq!(entry_err.get_priority()?, Priority::Error); + assert_eq!(entry_err.get_tag()?, "dlog_test_redirect"); + assert_eq!(entry_err.get_message()?, "test_redirect_stderr"); + + clear_buffer(BufferId::LogIdMain)?; + + Ok(()) +} + +fn main() -> Result<()> { + test_dlog_redirect() +} -- 2.34.1