blob: 3c7d41bd17a380150d642a7ae072a57e0798bb1e [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// non_upper_case_globals is for bindings
// dead_code is for conditional #[cfg(test)] usage of LAST_LOG lock
#![allow(non_upper_case_globals, dead_code)]
use std;
use std::error::Error;
use std::ffi::{CString, NulError};
use std::fmt;
use std::ptr;
use std::sync::Mutex;
use bindings::*;
lazy_static! {
pub static ref INSTANCES: Mutex<u8> = Mutex::new(0);
#[derive(Debug)]
pub static ref LAST_LOG: Mutex<String> = Mutex::new(String::new());
}
/// A safe wrapper of the C mosys logging facilities
///
/// The enum members are the various log levels defined in the C enum in mosys/log.h. Should be
/// kept in sync.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Log {
Emerg, // system is unusable
Alert, // action must be taken immediately
Crit, // critical conditions
Err, // error conditions
Warning, // warning conditions
Notice, // normal but significant condition
Info, // informational messages
Debug, // debug-level messages
Spew, // excessive debug messages
}
impl Log {
/// Initialize the logging system. Exactly one call to drop() is expected for every call to
/// this function.
pub fn init(prog_name: &str, threshold: Log) -> Result {
let prog_name_cstr = CString::new(prog_name).unwrap();
let threshold_cenum = Log::renum_to_cenum(threshold);
let mut ret: i32 = 0;
let mut m = INSTANCES.lock().unwrap();
*m += 1;
if *m == 1 {
// Safe because function doesn't mutate passed pointers and strings are null
// terminated.
unsafe {
ret = mosys_log_init(prog_name_cstr.as_ptr(), threshold_cenum, ptr::null_mut());
}
}
if ret != 0 {
return Err(LogError::Init(ret));
}
Log::set_threshold(threshold)
}
pub fn log(self, message: &str) -> Result {
let m = INSTANCES.lock().unwrap();
if *m < 1 {
return Err(LogError::NotReady);
}
let message_cstring = CString::new(message)?;
let threshold_cenum = Log::renum_to_cenum(self);
// Safe because function doesn't mutate passed pointers and strings are null terminated.
// Underlying impl is printf which is threadsafe in at least glibc.
unsafe {
lprintf(threshold_cenum, message_cstring.as_ptr());
}
#[cfg(test)]
{
*LAST_LOG.lock().unwrap() = message.to_string();
}
Ok(())
}
pub fn logln(self, message: &str) -> Result {
self.log(&format!("{}\n", message))
}
/// Get the current threshold for the logging system.
pub fn get_threshold() -> Log {
// Safe because log levels are always >0
unsafe {
let threshold = log_threshold_get() as u32;
return Log::cenum_to_renum(threshold);
}
}
/// Set the current threshold for the logging system. Returns LogError::InvalidThreshold
/// if the provided threshold is unknown to the logging subsystem.
pub fn set_threshold(threshold: Log) -> Result {
let t = Log::renum_to_cenum(threshold);
// Safe because all input states are handled by renum_to_cenum
unsafe {
match log_threshold_set(t) {
0 => Ok(()),
_ => Err(LogError::InvalidThreshold),
}
}
}
pub fn inc_threshold() {
let mut t = Log::get_threshold() as u32;
if t < Log::Spew as u32 {
t += 1;
Log::set_threshold(Log::from(t))
.expect("Unexpected return set_threshold; are bindings out of sync?");
}
}
/// Shut down the logging system. Exactly one call to this function is expected for every call
/// to init().
pub fn drop() {
let mut m = INSTANCES.lock().unwrap();
*m -= 1;
if *m == 0 {
// Safe because function only mutates state owned by C components.
unsafe {
mosys_log_halt();
}
}
}
fn renum_to_cenum(threshold: Log) -> u32 {
match threshold {
Log::Emerg => log_levels_LOG_EMERG,
Log::Alert => log_levels_LOG_ALERT,
Log::Crit => log_levels_LOG_CRIT,
Log::Err => log_levels_LOG_ERR,
Log::Warning => log_levels_LOG_WARNING,
Log::Notice => log_levels_LOG_NOTICE,
Log::Info => log_levels_LOG_INFO,
Log::Debug => log_levels_LOG_DEBUG,
Log::Spew => log_levels_LOG_SPEW,
}
}
fn cenum_to_renum(threshold: u32) -> Log {
match threshold {
log_levels_LOG_EMERG => Log::Emerg,
log_levels_LOG_ALERT => Log::Alert,
log_levels_LOG_CRIT => Log::Crit,
log_levels_LOG_ERR => Log::Err,
log_levels_LOG_WARNING => Log::Warning,
log_levels_LOG_NOTICE => Log::Notice,
log_levels_LOG_INFO => Log::Info,
log_levels_LOG_DEBUG => Log::Debug,
log_levels_LOG_SPEW => Log::Spew,
_ => Log::Spew,
}
}
}
impl From<u32> for Log {
fn from(n: u32) -> Log {
match n {
0 => Log::Emerg,
1 => Log::Alert,
2 => Log::Crit,
3 => Log::Err,
4 => Log::Warning,
5 => Log::Notice,
6 => Log::Info,
7 => Log::Debug,
8 => Log::Spew,
_ => Log::Spew,
}
}
}
#[derive(Debug)]
pub enum LogError {
Init(i32),
NotReady,
NullTerm(NulError),
InvalidThreshold,
}
impl fmt::Display for LogError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LogError::Init(code) => write!(f, "Logger initialization error code: {}", code),
LogError::NotReady => write!(f, "Logger not ready"),
LogError::NullTerm(ref err) => write!(f, "String null termination error: {}", err),
LogError::InvalidThreshold => write!(f, "Set invalid threshold"),
}
}
}
impl Error for LogError {
fn description(&self) -> &str {
match *self {
LogError::Init(_) => "Initialization",
LogError::NotReady => "Not ready",
LogError::NullTerm(ref err) => err.description(),
LogError::InvalidThreshold => "Invalid threshold",
}
}
fn cause(&self) -> Option<&Error> {
match *self {
LogError::NullTerm(ref err) => Some(err),
_ => None,
}
}
}
impl From<NulError> for LogError {
fn from(err: NulError) -> LogError {
LogError::NullTerm(err)
}
}
type Result = std::result::Result<(), LogError>;
#[cfg(test)]
mod tests {
use super::*;
fn setup() {
Log::init("progname", Log::Debug).expect("Should have succeeded");
}
fn teardown() {
Log::drop();
}
#[test]
fn test_init() {
setup();
teardown();
}
#[test]
fn test_double_init() {
setup();
Log::init("progname", Log::Warning).expect("Should have succeeded on second init");
let t = Log::get_threshold();
assert_eq!(t, Log::Warning, "Should have modified first init threshold");
Log::drop();
teardown();
}
#[test]
fn test_inc_threshold() {
setup();
Log::inc_threshold();
let t = Log::get_threshold();
assert_eq!(t, Log::Spew);
teardown();
}
}