use core::fmt::{self, Write};
use std::ffi::c_int;
use crate::log::{Level, Log};
pub struct Syslog;
mod internal {
use crate::system::syslog;
use std::ffi::{CStr, c_int};
const DOTDOTDOT_START: &[u8] = b"[...] ";
const DOTDOTDOT_END: &[u8] = b" [...]";
const MAX_MSG_LEN: usize = 960;
const NULL_BYTE_LEN: usize = 1; // for C string compatibility
const BUFSZ: usize = MAX_MSG_LEN + DOTDOTDOT_END.len() + NULL_BYTE_LEN;
pub struct SysLogMessageWriter {
buffer: [u8; BUFSZ],
cursor: usize,
facility: c_int,
priority: c_int,
}
// - whenever a SysLogMessageWriter has been constructed, a syslog message WILL be created
// for one specific event; this struct functions as a low-level interface for that message
// - the caller of the pub functions will have to take care never to `append` more bytes than
// are `available`, or a panic will occur.
// - the impl guarantees that after `line_break()`, there will be enough room available for at
// least a single UTF8 character sequence (which is true since MAX_MSG_LEN >= 10)
impl SysLogMessageWriter {
pub fn new(priority: c_int, facility: c_int) -> Self {
Self {
buffer: [0; BUFSZ],
cursor: 0,
priority,
facility,
}
}
pub fn append(&mut self, bytes: &[u8]) {
let num_bytes = bytes.len();
self.buffer[self.cursor..self.cursor + num_bytes].copy_from_slice(bytes);
self.cursor += num_bytes;
}
pub fn line_break(&mut self) {
self.append(DOTDOTDOT_END);
self.commit_to_syslog();
self.append(DOTDOTDOT_START);
}
fn commit_to_syslog(&mut self) {
self.append(&[0]);
let message = CStr::from_bytes_with_nul(&self.buffer[..self.cursor]).unwrap();
syslog(self.priority, self.facility, message);
self.cursor = 0;
}
pub fn available(&self) -> usize {
MAX_MSG_LEN - self.cursor
}
}
impl Drop for SysLogMessageWriter {
fn drop(&mut self) {
self.commit_to_syslog();
}
}
}
use internal::SysLogMessageWriter;
/// `floor_char_boundary` is currently unstable in Rust
fn floor_char_boundary(data: &str, mut index: usize) -> usize {
if index >= data.len() {
return data.len();
}
while !data.is_char_boundary(index) {
index -= 1;
}
index
}
/// This function REQUIRES that `message` is larger than `max_size` (or a panic will occur).
/// This function WILL return a non-zero result if `max_size` is large enough to fit
/// at least the first character of `message`.
fn suggested_break(message: &str, max_size: usize) -> usize {
// method A: try to split the message in two non-empty parts on an ASCII white space character
// method B: split on the utf8 character boundary that consumes the most data
if let Some(pos) = message.as_bytes()[1..max_size]
.iter()
.rposition(|c| c.is_ascii_whitespace())
{
// since pos+1 contains ASCII whitespace, it acts as a valid utf8 boundary as well
pos + 1
} else {
floor_char_boundary(message, max_size)
}
}
impl Write for SysLogMessageWriter {
fn write_str(&mut self, mut message: &str) -> fmt::Result {
while message.len() > self.available() {
let truncate_boundary = suggested_break(message, self.available());
let left = &message[..truncate_boundary];
let right = &message[truncate_boundary..];
self.append(left.as_bytes());
self.line_break();
// This loop while terminate, since either of the following is true:
// 1. truncate_boundary is strictly positive:
// message.len() has strictly decreased, and self.available() has not decreased
// 2. truncate_boundary is zero:
// message.len() has remained unchanged, but self.available() has strictly increased;
// this latter is true since, for truncate_boundary to be 0, self.available() must
// have been not large enough to fit a single UTF8 character
message = right;
}
self.append(message.as_bytes());
Ok(())
}
}
const FACILITY: c_int = libc::LOG_AUTH;
impl Log for Syslog {
fn log(&self, level: Level, args: &dyn fmt::Display) {
let priority = match level {
Level::Error => libc::LOG_ERR,
Level::Warn => libc::LOG_WARNING,
Level::Info => libc::LOG_INFO,
Level::Debug => libc::LOG_DEBUG,
};
let mut writer = SysLogMessageWriter::new(priority, FACILITY);
let _ = write!(writer, "{}", args);
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use super::*;
#[test]
fn can_write_to_syslog() {
Syslog.log(Level::Info, &format_args!("Hello World!"));
}
#[test]
fn can_handle_multiple_writes() {
let mut writer = SysLogMessageWriter::new(libc::LOG_DEBUG, FACILITY);
for i in 1..20 {
let _ = write!(writer, "{}", "Test 123 ".repeat(i));
}
}
#[test]
fn can_truncate_syslog() {
Syslog.log(
Level::Info,
&format_args!("This is supposed to be a very long syslog message but idk what to write, so I am just going to tell you about the time I tried to make coffee with a teapot. So I woke up one morning and decided to make myself a pot of coffee, however after all the wild coffee parties and mishaps the coffee pot had evetually given it's last cup on a tragic morning I call wednsday. So it came to, that the only object capable of giving me hope for the day was my teapot. As I stood in the kitchen and reached for my teapot it, as if sensing the impending horrors that awaited the innocent little teapot, emmited a horse sheak of desperation. \"three hundred and seven\", it said. \"What?\" I asked with a voice of someone who clearly did not want to be bothered until he had his daily almost medically necessary dose of caffine. \"I am a teapot\" it responded with a voice of increasing forcefulness. \"I am a teapot, not a coffee pot\". It was then, in my moments of confusion that my brain finally understood, this was a teapot."),
);
}
#[test]
fn can_truncate_syslog_with_no_spaces() {
Syslog.log(
Level::Info,
&format_args!("iwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercases"),
);
}
#[test]
fn will_not_break_utf8() {
let mut writer = SysLogMessageWriter::new(libc::LOG_DEBUG, FACILITY);
let _ = write!(writer, "{}ยข", "x".repeat(959));
}
}