syslog-rs 0.2.1

A native Rust implementation of the glibc/libc syslog.
Documentation
/*-
* syslog-rs - a syslog client translated from libc to rust
* Copyright (C) 2021  Aleksandr Morozov
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
* 
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

use std::ops::{BitAnd, Shl};
use std::mem::zeroed;
use std::ffi::CString;

use super::error::{SyRes, SyslogError, SyslogErrCode};
use super::throw_error;

bitflags! {
    /// Flags  which  control  the  operation  of openlog() and 
    /// subsequent calls to syslog.
    pub struct LogStat: libc::c_int 
    {
        const LOG_PID = libc::LOG_PID;
        
        /// Write directly to the system console if there is an error 
        /// while sending to the system logger.
        const LOG_CONS = libc::LOG_CONS;

        /// The converse of LOG_NDELAY; opening of the connection is delayed 
        /// until syslog() is called. (This is the default behaviour,and need 
        /// not be specified.)
        const LOG_ODELAY = libc::LOG_ODELAY;

        /// Open the connection immediately
        const LOG_NDELAY = libc::LOG_NDELAY;

        /// Don't wait for child processes that may have been created 
        /// while logging the message
        const LOG_NOWAIT = libc::LOG_NOWAIT;
        
        /// Also log the message to stderr
        const LOG_PERROR = 0x20;
    }
}

bitflags! {
    pub(crate) struct LogMask: libc::c_int 
    {
        const LOG_FACMASK = libc::LOG_FACMASK;
        const LOG_PRIMASK = libc::LOG_PRIMASK;
    }
}

bitflags! {
    /// This determines the importance of the message
    pub struct Priority: libc::c_int 
    {
        /// system is unusable
        const LOG_EMERG = libc::LOG_EMERG;

        /// action must be taken immediately
        const LOG_ALERT = libc::LOG_ALERT;

        /// critical conditions
        const LOG_CRIT = libc::LOG_CRIT;

        /// error conditions
        const LOG_ERR = libc::LOG_ERR;

        /// warning conditions
        const LOG_WARNING = libc::LOG_WARNING;

        /// normal, but significant, condition
        const LOG_NOTICE = libc::LOG_NOTICE;
        
        /// informational message
        const LOG_INFO = libc::LOG_INFO;

        /// debug-level message
        const LOG_DEBUG = libc::LOG_DEBUG;
    }
}


bitflags! {
    /// The facility argument is used to specify what type of program 
    /// is logging the message.
    pub struct LogFacility: libc::c_int 
    {
        /// kernel messages (these can't be generated from user processes)
        const LOG_KERN = libc::LOG_KERN;

        /// (default) generic user-level messages
        const LOG_USER = libc::LOG_USER;

        /// mail subsystem
        const LOG_MAIL = libc::LOG_MAIL;

        /// system daemons without separate facility value
        const LOG_DAEMON = libc::LOG_DAEMON;
        
        /// security/authorization messages
        const LOG_AUTH = libc::LOG_AUTH;

        /// messages generated internally by syslogd(8)
        const LOG_SYSLOG = libc::LOG_SYSLOG;

        /// line printer subsystem
        const LOG_LPR = libc::LOG_LPR;

        /// USENET news subsystem
        const LOG_NEWS = libc::LOG_NEWS;

        /// UUCP subsystem
        const LOG_UUCP = libc::LOG_UUCP;

        /// reserved for local use
        const LOG_LOCAL0 = libc::LOG_LOCAL0;

        /// reserved for local use
        const LOG_LOCAL1 = libc::LOG_LOCAL1;

        /// reserved for local use
        const LOG_LOCAL2 = libc::LOG_LOCAL2;
        
        /// reserved for local use
        const LOG_LOCAL3 = libc::LOG_LOCAL3;

        /// reserved for local use
        const LOG_LOCAL4 = libc::LOG_LOCAL4;

        /// reserved for local use
        const LOG_LOCAL5 = libc::LOG_LOCAL5;

        /// reserved for local use
        const LOG_LOCAL6 = libc::LOG_LOCAL6;
        
        /// reserved for local use
        const LOG_LOCAL7 = libc::LOG_LOCAL7;
    }
}

/// max hostname size
pub const MAXHOSTNAMELEN: usize = 256;

/// mask to extract facility part
pub const LOG_FACMASK: i32 = 0x03f8;

/// Maximum number of characters of syslog message
pub const MAXLINE: u32 = 8192;

/// RFC5424 defined value.
pub const NILVALUE: &'static str = "-";

/// Unpriv socket
pub const PATH_LOG: &'static str = "/var/run/log";

/// Priviledged socket
pub const PATH_LOG_PRIV: &'static str = "/var/run/logpriv";

/// backward compatibility
pub const PATH_OLDLOG: &'static str = "/dev/log";

/// OSX compat
pub const PATH_OSX: &'static str = "/var/run/syslog";

lazy_static! {
    /// path to the console dev
    pub static ref PATH_CONSOLE: CString = CString::new("/dev/console").unwrap();
}


/// LOG_MASK is used to create the priority mask in setlogmask. 
/// For a single Priority mask
/// used with [Priority]
/// can be used with | & ! bit operations LOG_MASK()
///
/// # Examples
/// 
/// ```
///     LOG_MASK!(Priority::LOG_ALERT) | LOG_MASK!(Priority::LOG_INFO)
/// ```
#[macro_export]
macro_rules! LOG_MASK 
{
    ($($arg:tt)*) => (
        (1 << $($arg)*)
    )
}

/// LOG_MASK is used to create the priority mask in setlogmask
/// For a mask UPTO specified
/// used with [Priority]
///
/// # Examples
/// 
/// ```
///     LOG_UPTO!(Priority::LOG_ALERT)
/// ```
#[macro_export]
macro_rules! LOG_UPTO 
{
    ($($arg:tt)*) => (
        ((1 << (($($arg)*) + 1)) - 1)
    )
}

/// Returns the static configuration for internal log
pub fn get_internal_log() -> libc::c_int
{
    return 
        Priority::LOG_ERR.bits() | 
        (LogStat::LOG_CONS| LogStat::LOG_PERROR| LogStat::LOG_PID).bits();
}

impl Shl<Priority> for i32
{
    type Output = i32;

    fn shl(self, rhs: Priority) -> i32 
    {
        let lhs = self;
        return lhs << rhs.bits();
    }
}

impl BitAnd<Priority> for i32
{
    type Output = i32;

    #[inline]
    fn bitand(self, rhs: Priority) -> i32
    {
        return self & rhs.bits();
    }
}

impl BitAnd<LogMask> for Priority 
{
    type Output = Priority;

    #[inline]
    fn bitand(self, rhs: LogMask) -> Self::Output
    {
        return Self {bits: self.bits() & rhs.bits()};
    }
}

impl BitAnd<LogMask> for LogFacility 
{
    type Output = LogFacility;

    #[inline]
    fn bitand(self, rhs: LogMask) -> Self::Output
    {
        return Self {bits: self.bits() & rhs.bits()};
    }
}

impl BitAnd<LogMask> for i32 
{
    type Output = i32;

    #[inline]
    fn bitand(self, rhs: LogMask) -> i32
    {
        return self & rhs.bits();
    }
}

/// A function which uses [libc::iovec] to send message to FD.
/// This function uses unsafe code
///
/// # Arguments
/// 
/// * `fd` - a valid file descripter. It is not checked if it is valid
///
/// * `msg` - a reference on array of data
///
/// * `newline` - a new line string ref i.e "\n" or "\r\n"
pub(crate) fn send_to_stderr(
    fd: libc::c_int, 
    msg: &mut [u8], 
    newline: &mut String)
{
    let mut iov1: libc::iovec = unsafe {zeroed()};
    let mut iov2: libc::iovec = unsafe {zeroed()};

    iov1.iov_base = msg.as_mut_ptr() as *mut _ as *mut libc::c_void;
    iov1.iov_len = msg.len();

    iov2.iov_base = newline.as_mut_ptr() as *mut libc::c_void;
    iov2.iov_len = 1;

    unsafe
    {
        libc::writev(
            fd, 
            [iov1, iov2].as_ptr() as *const libc::iovec, 
            2
        );
    }
}

/// This function trancated 1 last UTF8 character from the string.
///
/// # Arguments
///
/// * `lt` - a string which is trucated
/// 
/// # Returns
/// 
/// * A reference to the ctruncated string
pub(crate) fn truncate(lt: &str) -> &str
{
    let ltt =
        match lt.char_indices().nth(lt.len()-1) 
        {
            None => lt,
            Some((idx, _)) => &lt[..idx],
        };
    return ltt;
}

/// Trancates the string up to n UTF8 chars if string exceeds size
/// 
/// # Arguments
///
/// * `lt` - a string to truncate
///
/// * `n` - a size
/// 
/// # Returns 
///
/// * The new instance of String even if no action was required
pub(crate) fn truncate_n(lt: &str, n: usize) -> String
    {
        let ltt =
            match lt.char_indices().nth(n) 
            {
                None => lt,
                Some((idx, _)) => &lt[..idx],
            };

        return ltt.to_string();
    }

/// This function validates the pri for the incorrects bits set.
/// If bits are set incorrectly, resets the invalid bits with:
/// *pri & (LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK).
///
/// # Arguments
///
/// * `pri` - a priority bits
///
/// # Returns
/// 
/// * A [SyRes]. Ok() when valid or Err with error message
pub(crate) fn check_invalid_bits(pri: &mut i32) -> SyRes<()>
{
    if (*pri & !(LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK )) != 0
    {
        let pri_old = *pri;
        
        *pri =  *pri & (LogMask::LOG_PRIMASK | LogMask::LOG_FACMASK).bits();

        throw_error!("unknwon facility/priority: {:x}", pri_old);
    }

    return Ok(());
}


#[test]
fn test_truncate()
{
    let test = "cat\n";

    let trunc = truncate(test);

    assert_eq!("cat", trunc);
}

#[test]
fn test_priority_shl()
{
    assert_eq!((1 << 5), (1 << Priority::LOG_NOTICE));
}