diff --git a/Cargo.lock b/Cargo.lock index e2ad91c..6bca5ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "atty" version = "0.2.14" @@ -304,6 +310,7 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" name = "laurel" version = "0.5.5" dependencies = [ + "anyhow", "bencher", "bindgen", "caps", @@ -322,6 +329,7 @@ dependencies = [ "signal-hook", "simple_logger", "syslog", + "thiserror", "tinyvec", "toml", ] diff --git a/Cargo.toml b/Cargo.toml index 016fdc4..c47cbbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,8 @@ tinyvec = { version = "1", features = ["alloc"] } log = "0.4" simple_logger = ">= 1" syslog = "6" +thiserror = "1" +anyhow = "1" [target.'cfg(target_os = "linux")'.dependencies] caps = "0.5" diff --git a/src/bin/laurel/main.rs b/src/bin/laurel/main.rs index 1861279..9ce5ee9 100644 --- a/src/bin/laurel/main.rs +++ b/src/bin/laurel/main.rs @@ -3,7 +3,7 @@ use getopts::Options; use std::env; -use std::error::Error; +use std::error::Error as StdError; use std::fs; use std::io::{self, BufRead, BufReader, BufWriter, Read, Write}; use std::ops::AddAssign; @@ -17,6 +17,8 @@ use std::sync::{ }; use std::time::{Duration, SystemTime}; +use anyhow::{anyhow, Context}; + use nix::unistd::{chown, execve, Uid, User}; #[cfg(target_os = "linux")] use nix::unistd::{setresgid, setresuid}; @@ -58,24 +60,22 @@ impl AddAssign for Stats { /// environment variables from arbitrary processes /// (/proc/$PID/environ). #[cfg(target_os = "linux")] -fn drop_privileges(runas_user: &User) -> Result<(), Box> { +fn drop_privileges(runas_user: &User) -> anyhow::Result<()> { set_keepcaps(true)?; let uid = runas_user.uid; let gid = runas_user.gid; - setresgid(gid, gid, gid).map_err(|e| format!("setresgid({}): {}", gid, e))?; - setresuid(uid, uid, uid).map_err(|e| format!("setresuid({}): {}", uid, e))?; + setresgid(gid, gid, gid).with_context(|| format!("setresgid({gid})"))?; + setresuid(uid, uid, uid).with_context(|| format!("setresuid({uid})"))?; #[cfg(feature = "procfs")] { let mut capabilities = std::collections::HashSet::new(); capabilities.insert(Capability::CAP_SYS_PTRACE); capabilities.insert(Capability::CAP_DAC_READ_SEARCH); - caps::set(None, CapSet::Permitted, &capabilities) - .map_err(|e| format!("set permitted capabilities: {}", e))?; - caps::set(None, CapSet::Effective, &capabilities) - .map_err(|e| format!("set effective capabilities: {}", e))?; + caps::set(None, CapSet::Permitted, &capabilities).context("set permitted capabilities")?; + caps::set(None, CapSet::Effective, &capabilities).context("set effective capabilities")?; caps::set(None, CapSet::Inheritable, &capabilities) - .map_err(|e| format!("set inheritable capabilities: {}", e))?; + .context("set inheritable capabilities")?; } set_keepcaps(false)?; @@ -97,18 +97,17 @@ impl Logger { self.output.flush().unwrap(); } - fn new(def: &Logfile, dir: &Path) -> Result> { + fn new(def: &Logfile, dir: &Path) -> anyhow::Result { match &def.file { p if p.as_os_str() == "-" => Ok(Logger { prefix: def.line_prefix.clone(), output: BufWriter::new(Box::new(io::stdout())), }), - p if p.has_root() && p.parent().is_none() => Err(format!( + p if p.has_root() && p.parent().is_none() => Err(anyhow!( "invalid file directory={} file={}", dir.to_string_lossy(), p.to_string_lossy() - ) - .into()), + )), p => { let mut filename = dir.to_path_buf(); filename.push(p); @@ -116,7 +115,7 @@ impl Logger { for user in &def.clone().users.unwrap_or_default() { rot = rot.with_uid( User::from_name(user)? - .ok_or_else(|| format!("user {} not found", &user))? + .ok_or_else(|| anyhow!("user {user} not found"))? .uid, ); } @@ -135,7 +134,7 @@ impl Logger { } } -fn run_app() -> Result<(), Box> { +fn run_app() -> Result<(), Box> { let args: Vec = env::args().collect(); let mut opts = Options::new(); @@ -158,7 +157,7 @@ fn run_app() -> Result<(), Box> { let config: Config = match matches.opt_str("c") { Some(f_name) => { if fs::metadata(&f_name) - .map_err(|e| format!("stat {}: {}", &f_name, &e))? + .with_context(|| format!("stat: {f_name}"))? .permissions() .mode() & 0o002 @@ -166,12 +165,9 @@ fn run_app() -> Result<(), Box> { { return Err(format!("Config file {} must not be world-writable", f_name).into()); } - let lines = fs::read(&f_name).map_err(|e| format!("read {}: {}", &f_name, &e))?; - toml::from_str( - &String::from_utf8(lines) - .map_err(|_| format!("parse: {}: contains invalid UTF-8 sequences", &f_name))?, - ) - .map_err(|e| format!("parse {}: {}", f_name, e))? + let lines = fs::read(&f_name).with_context(|| format!("read(: {f_name}"))?; + toml::from_str(&String::from_utf8(lines).with_context(|| format!("parse: {f_name}"))?) + .with_context(|| format!("parse {f_name}"))? } None => Config::default(), }; @@ -184,7 +180,7 @@ fn run_app() -> Result<(), Box> { Input::Stdin => Box::new(unsafe { std::fs::File::from_raw_fd(0) }), Input::Unix(path) => Box::new( UnixStream::connect(path) - .map_err(|e| format!("connect: {}: {}", path.to_string_lossy(), e))?, + .with_context(|| format!("connect: {}", path.to_string_lossy()))?, ), }; @@ -217,7 +213,7 @@ fn run_app() -> Result<(), Box> { } if dir .metadata() - .map_err(|e| format!("stat {}: {}", dir.to_string_lossy(), &e))? + .with_context(|| format!("stat {}", dir.to_string_lossy()))? .permissions() .mode() & 0o002 @@ -230,15 +226,15 @@ fn run_app() -> Result<(), Box> { } } else { fs::create_dir_all(&dir) - .map_err(|e| format!("create_dir: {}: {}", dir.to_string_lossy(), e))?; + .with_context(|| format!("create_dir: {}", dir.to_string_lossy()))?; } chown(&dir, Some(runas_user.uid), Some(runas_user.gid)) - .map_err(|e| format!("chown: {}: {}", dir.to_string_lossy(), e))?; + .with_context(|| format!("chown: {}", dir.to_string_lossy()))?; fs::set_permissions(&dir, PermissionsExt::from_mode(0o755)) - .map_err(|e| format!("chmod: {}: {}", dir.to_string_lossy(), e))?; + .with_context(|| format!("chmod: {}", dir.to_string_lossy()))?; let mut debug_logger = if let Some(l) = &config.debug.log { - Some(Logger::new(l, &dir).map_err(|e| format!("can't create debug logger: {}", e))?) + Some(Logger::new(l, &dir).context("can't create debug logger")?) } else { None }; @@ -293,13 +289,12 @@ fn run_app() -> Result<(), Box> { let emit_fn_drop; let emit_fn_log; - let mut logger = Logger::new(&config.auditlog, &dir) - .map_err(|e| format!("can't create audit logger: {}", e))?; + let mut logger = Logger::new(&config.auditlog, &dir).context("can't create audit logger")?; if let laurel::config::FilterAction::Log = config.filter.filter_action { log::info!("Logging filtered audit records"); - let mut filter_logger = Logger::new(&config.filterlog, &dir) - .map_err(|e| format!("can't create filterlog logger: {}", e))?; + let mut filter_logger = + Logger::new(&config.filterlog, &dir).context("can't create filterlog logger")?; emit_fn_log = move |e: &Event| { if e.filter { filter_logger.log(e) @@ -344,7 +339,7 @@ fn run_app() -> Result<(), Box> { if let Some(ref mut l) = error_logger { l.write_all(line) .and_then(|_| l.flush()) - .map_err(|e| format!("write log: {}", e))?; + .context("write log")?; } let line = String::from_utf8_lossy(line).replace('\n', ""); log::error!("Error {} processing msg: {}", e, &line); @@ -373,7 +368,7 @@ fn run_app() -> Result<(), Box> { line.clear(); if input .read_until(b'\n', &mut line) - .map_err(|e| format!("read from stdin: {}", e))? + .context("read from stdin")? == 0 { break; @@ -387,7 +382,7 @@ fn run_app() -> Result<(), Box> { if let Some(ref mut l) = error_logger { l.write_all(&line) .and_then(|_| l.flush()) - .map_err(|e| format!("write log: {}", e))?; + .context("write log")?; } let line = String::from_utf8_lossy(&line).replace('\n', ""); log::error!("Error {} processing msg: {}", e, &line); @@ -419,6 +414,7 @@ fn run_app() -> Result<(), Box> { if dump_state_last_t.elapsed()? >= *p { coalesce .dump_state(&mut dl.output) + // .context("dump state")?; .map_err(|e| format!("dump state: {}", e))?; dump_state_last_t = SystemTime::now(); } diff --git a/src/coalesce.rs b/src/coalesce.rs index baa4faf..c109932 100644 --- a/src/coalesce.rs +++ b/src/coalesce.rs @@ -8,7 +8,7 @@ use serde_json::json; use crate::constants::{msg_type::*, ARCH_NAMES, SYSCALL_NAMES}; use crate::label_matcher::LabelMatcher; -use crate::parser::parse; +use crate::parser::{parse, ParseError}; use crate::proc::{ContainerInfo, ProcTable, Process, ProcessKey}; #[cfg(all(feature = "procfs", target_os = "linux"))] use crate::procfs; @@ -17,6 +17,8 @@ use crate::sockaddr::SocketAddr; use crate::types::*; use crate::userdb::UserDB; +use thiserror::Error; + #[derive(Clone)] pub struct Settings<'a> { /// Generate ARGV and ARGV_STR from EXECVE @@ -74,6 +76,16 @@ impl Default for Settings<'_> { } } +#[derive(Debug, Error)] +pub enum CoalesceError { + #[error("{0}")] + Parse(ParseError), + #[error("duplicate event id {0}")] + DuplicateEvent(EventID), + #[error("Event id {0} for EOE marker not found")] + SpuriousEOE(EventID), +} + /// Coalesce collects Audit Records from individual lines and assembles them to Events pub struct Coalesce<'a> { /// Events that are being collected/processed @@ -1005,9 +1017,9 @@ impl<'a> Coalesce<'a> { /// /// The line is consumed and serves as backing store for the /// EventBody objects. - pub fn process_line(&mut self, line: Vec) -> Result<(), Box> { + pub fn process_line(&mut self, line: Vec) -> Result<(), CoalesceError> { let skip_enriched = self.settings.translate_universal && self.settings.translate_userdb; - let (node, typ, id, rv) = parse(line, skip_enriched)?; + let (node, typ, id, rv) = parse(line, skip_enriched).map_err(CoalesceError::Parse)?; let nid = (node.clone(), id); // clean out state every EXPIRE_PERIOD @@ -1024,12 +1036,12 @@ impl<'a> Coalesce<'a> { if typ == EOE { if self.done.contains(&nid) { - return Err(format!("duplicate event id {}", id).into()); + return Err(CoalesceError::DuplicateEvent(id)); } let ev = self .inflight .remove(&nid) - .ok_or(format!("Event id {} for EOE marker not found", &id))?; + .ok_or(CoalesceError::SpuriousEOE(id))?; self.emit_event(ev); } else if typ.is_multipart() { // kernel-level messages @@ -1055,7 +1067,7 @@ impl<'a> Coalesce<'a> { } else { // user-space messages if self.done.contains(&nid) { - return Err(format!("duplicate event id {}", id).into()); + return Err(CoalesceError::DuplicateEvent(id)); } let mut ev = Event::new(node, id); ev.body.insert(typ, EventValues::Single(rv)); diff --git a/src/label_matcher.rs b/src/label_matcher.rs index 9f3cc2c..312ac7a 100644 --- a/src/label_matcher.rs +++ b/src/label_matcher.rs @@ -1,4 +1,3 @@ -use std::error::Error; use std::fmt; use regex::bytes::RegexSet; @@ -14,7 +13,7 @@ pub struct LabelMatcher { } impl LabelMatcher { - pub fn new(exprs: &[(&str, &str)]) -> Result> { + pub fn new(exprs: &[(&str, &str)]) -> Result { let mut regexes = Vec::with_capacity(exprs.len()); let mut tags = Vec::with_capacity(exprs.len()); for (r, t) in exprs { diff --git a/src/parser.rs b/src/parser.rs index 3a9f73c..47870bb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -12,6 +12,24 @@ use nom::{ use nom::character::complete::{i64 as dec_i64, u16 as dec_u16, u32 as dec_u32, u64 as dec_u64}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ParseError { + #[error("cannot parse header: {}", String::from_utf8_lossy(.0))] + MalformedHeader(Vec), + #[error("cannot parse body: {}", String::from_utf8_lossy(.0))] + MalformedBody(Vec), + #[error("garbage at end of message: {}", String::from_utf8_lossy(.0))] + TrailingGarbage(Vec), + #[error("{id} ({ty}) can't hex-decode {}", String::from_utf8_lossy(.hex_str))] + HexDecodeError { + ty: MessageType, + id: EventID, + hex_str: Vec, + }, +} + /// Parse a single log line as produced by _auditd(8)_ /// /// If `skip_enriched` is set and _auditd_ has been configured to @@ -22,26 +40,15 @@ use nom::character::complete::{i64 as dec_i64, u16 as dec_u16, u32 as dec_u32, u pub fn parse( mut raw: Vec, skip_enriched: bool, -) -> Result<(Option>, MessageType, EventID, Record), String> { - let (rest, (nd, ty, id)) = parse_header(&raw).map_err(|e| { - format!( - "cannot parse header: {}", - e.map_input(String::from_utf8_lossy) - ) - })?; - - let (rest, body) = parse_body(rest, ty, skip_enriched).map_err(|e| { - format!( - "cannot parse body: {}", - e.map_input(String::from_utf8_lossy) - ) - })?; +) -> Result<(Option>, MessageType, EventID, Record), ParseError> { + let (rest, (nd, ty, id)) = + parse_header(&raw).map_err(|_| ParseError::MalformedHeader(raw.clone()))?; + + let (rest, body) = parse_body(rest, ty, skip_enriched) + .map_err(|_| ParseError::MalformedBody(rest.to_vec()))?; if !rest.is_empty() { - return Err(format!( - "garbage at end of message: {}", - String::from_utf8_lossy(rest) - )); + return Err(ParseError::TrailingGarbage(rest.to_vec())); } let nd = nd.map(|s| s.to_vec()); @@ -82,10 +89,12 @@ pub fn parse( let d = unsafe { str::from_utf8_unchecked(&raw[stride.start + 2 * i..stride.start + 2 * i + 2]) }; - raw[stride.start + i] = u8::from_str_radix(d, 16).map_err(|_| { - let hex_str = unsafe { str::from_utf8_unchecked(&raw[stride.clone()]) }; - format!("{} ({}) can't hex-decode {}", id, ty, hex_str) - })?; + raw[stride.start + i] = + u8::from_str_radix(d, 16).map_err(|_| ParseError::HexDecodeError { + id, + ty, + hex_str: raw[stride.clone()].to_vec(), + })?; } } @@ -537,7 +546,7 @@ mod test { use super::msg_type::*; use super::*; - fn do_parse(text: T) -> Result<(Option>, MessageType, EventID, Record), String> + fn do_parse(text: T) -> Result<(Option>, MessageType, EventID, Record), ParseError> where T: AsRef<[u8]>, { diff --git a/src/proc.rs b/src/proc.rs index 38b715d..c388277 100644 --- a/src/proc.rs +++ b/src/proc.rs @@ -131,7 +131,9 @@ impl Process { /// Generate a shadow process table entry from /proc/$PID for a given PID #[cfg(all(feature = "procfs", target_os = "linux"))] pub fn parse_proc(pid: u32) -> Result> { - procfs::parse_proc_pid(pid).map(|p| p.into()) + procfs::parse_proc_pid(pid) + .map(|p| p.into()) + .map_err(|e| e.into()) } } diff --git a/src/procfs.rs b/src/procfs.rs index e3a9f80..9c2653a 100644 --- a/src/procfs.rs +++ b/src/procfs.rs @@ -1,4 +1,3 @@ -use std::error::Error; use std::ffi::OsStr; use std::fs::{read_dir, read_link, File, Metadata}; use std::io::{BufRead, BufReader, Read, Write}; @@ -11,14 +10,31 @@ use nix::sys::time::TimeSpec; use nix::time::{clock_gettime, ClockId}; use nix::unistd::{sysconf, SysconfVar}; +use thiserror::Error; + lazy_static! { /// kernel clock ticks per second static ref CLK_TCK: u64 = sysconf(SysconfVar::CLK_TCK).unwrap().unwrap() as u64; } -/// Read contents of file, return buffer. -fn slurp_file(path: impl AsRef) -> Result, Box> { +#[derive(Debug, Error)] +pub enum ProcFSError { + #[error("can't read /proc/{pid}/(obj): {err}")] + PidFile { + pid: u32, + obj: &'static str, + err: std::io::Error, + }, + #[error("can't enumerate processes: {0}")] + Enum(std::io::Error), + #[error("can't get field {0}")] + Field(&'static str), + #[error("{0}: {1}")] + Errno(&'static str, nix::errno::Errno), +} + +fn slurp_file>(path: P) -> Result, std::io::Error> { let f = File::open(path)?; let mut r = BufReader::with_capacity(1 << 16, f); r.fill_buf()?; @@ -27,14 +43,20 @@ fn slurp_file(path: impl AsRef) -> Result, Box> { Ok(buf) } +/// Read contents of file, return buffer. +fn slurp_pid_obj(pid: u32, obj: &'static str) -> Result, ProcFSError> { + let path = format!("/proc/{pid}/{obj}"); + slurp_file(path).map_err(|err| ProcFSError::PidFile { pid, obj, err }) +} + type Environment = Vec<(Vec, Vec)>; /// Returns set of environment variables that match pred for a given process -pub fn get_environ(pid: u32, pred: F) -> Result> +pub fn get_environ(pid: u32, pred: F) -> Result where F: Fn(&[u8]) -> bool, { - let buf = slurp_file(format!("/proc/{}/environ", pid))?; + let buf = slurp_pid_obj(pid, "environ")?; let mut res = Vec::new(); for e in buf.split(|c| *c == 0) { @@ -49,9 +71,9 @@ where } /// Returns all currently valid process IDs -pub fn get_pids() -> Result, Box> { +pub fn get_pids() -> Result, ProcFSError> { Ok(read_dir("/proc") - .map_err(|e| format!("read_dir: /proc: {}", e))? + .map_err(ProcFSError::Enum)? .flatten() .filter_map(|e| u32::from_str(e.file_name().to_string_lossy().as_ref()).ok()) .collect::>()) @@ -86,15 +108,14 @@ pub(crate) struct ProcPidInfo { } /// Parses information from /proc entry corresponding to process pid -pub(crate) fn parse_proc_pid(pid: u32) -> Result> { - let buf = slurp_file(format!("/proc/{}/stat", pid)) - .map_err(|e| format!("read /proc/{}/stat: {}", pid, e))?; +pub(crate) fn parse_proc_pid(pid: u32) -> Result { + let buf = slurp_pid_obj(pid, "stat")?; // comm may contain whitespace and ")", skip over it. let pid_end = buf .iter() .enumerate() .find(|(_, c)| **c == b' ') - .ok_or("end of 'pid' field not found")? + .ok_or(ProcFSError::Field("pid"))? .0; let stat_pid = &buf[..pid_end]; @@ -102,13 +123,13 @@ pub(crate) fn parse_proc_pid(pid: u32) -> Result> { .iter() .enumerate() .rfind(|(_, c)| **c == b')') - .ok_or("end of 'cmd' field not found")? + .ok_or(ProcFSError::Field("comm"))? .0; let stat = &buf[comm_end + 2..] .split(|c| *c == b' ') .collect::>(); - let comm = slurp_file(format!("/proc/{}/comm", pid)) + let comm = slurp_pid_obj(pid, "comm") .map(|mut s| { s.truncate(s.len() - 1); s @@ -119,9 +140,12 @@ pub(crate) fn parse_proc_pid(pid: u32) -> Result> { .map(|p| Vec::from(p.as_os_str().as_bytes())) .ok(); - let pid = u32::from_str(String::from_utf8_lossy(stat_pid).as_ref())?; - let ppid = u32::from_str(String::from_utf8_lossy(stat[1]).as_ref())?; - let starttime = u64::from_str(String::from_utf8_lossy(stat[19]).as_ref())?; + let pid = u32::from_str(String::from_utf8_lossy(stat_pid).as_ref()) + .map_err(|_| ProcFSError::Field("pid"))?; + let ppid = u32::from_str(String::from_utf8_lossy(stat[1]).as_ref()) + .map_err(|_| ProcFSError::Field("ppid"))?; + let starttime = u64::from_str(String::from_utf8_lossy(stat[19]).as_ref()) + .map_err(|_| ProcFSError::Field("starttime"))?; // Use the boottime-based clock to calculate process start // time, convert to Unix-epoch-based-time. @@ -129,12 +153,15 @@ pub(crate) fn parse_proc_pid(pid: u32) -> Result> { tv_sec: (starttime / *CLK_TCK) as _, tv_nsec: ((starttime % *CLK_TCK) * (1_000_000_000 / *CLK_TCK)) as _, }); + #[cfg(not(target_os = "linux"))] + let proc_age = TimeSpec::from(std::time::Duration::ZERO); + #[cfg(target_os = "linux")] let proc_age = clock_gettime(ClockId::CLOCK_BOOTTIME) - .map_err(|e| format!("clock_gettime: {}", e))? + .map_err(|e| ProcFSError::Errno("clock_gettime(CLOCK_BOOTTIME)", e))? - proc_boottime; let starttime = { let lt = clock_gettime(ClockId::CLOCK_REALTIME) - .map_err(|e| format!("clock_gettime: {}", e))? + .map_err(|e| ProcFSError::Errno("clock_gettime(CLOCK_REALTIME)", e))? - proc_age; (lt.tv_sec() * 1000 + lt.tv_nsec() / 1_000_000) as u64 }; @@ -164,11 +191,11 @@ fn extract_sha256(buf: &[u8]) -> Option<&[u8]> { } /// Parses "container id" (some SHA256 sum) from /proc/pid/cgroup -pub(crate) fn parse_proc_pid_cgroup(pid: u32) -> Result>, Box> { - parse_cgroup_buf(&slurp_file(format!("/proc/{}/cgroup", pid))?) +pub(crate) fn parse_proc_pid_cgroup(pid: u32) -> Result>, ProcFSError> { + parse_cgroup_buf(&slurp_pid_obj(pid, "cgroup")?) } -fn parse_cgroup_buf(buf: &[u8]) -> Result>, Box> { +fn parse_cgroup_buf(buf: &[u8]) -> Result>, ProcFSError> { for line in buf.split(|c| *c == b'\n') { let dir = line.split(|&c| c == b':').nth(2); if dir.is_none() { diff --git a/src/sockaddr.rs b/src/sockaddr.rs index 506f621..b102306 100644 --- a/src/sockaddr.rs +++ b/src/sockaddr.rs @@ -5,9 +5,10 @@ include!(concat!(env!("OUT_DIR"), "/sockaddr.rs")); use std::convert::TryInto; -use std::error::Error; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; +use thiserror::Error; + /* pub struct SocketAddrLL { pub protocol: u16, @@ -73,9 +74,17 @@ pub enum SocketAddr { VM(SocketAddrVM), } -fn get_sock(buf: &[u8]) -> Result { +#[derive(Debug, Error)] +pub enum SocketAddrError { + #[error("buffer too short")] + BufferTooShort, + #[error("unrecognized socket family {0}")] + UnrecognizedFamily(u16), +} + +fn get_sock(buf: &[u8]) -> Result { if buf.len() < std::mem::size_of::() { - Err("buffer too short".into()) + Err(SocketAddrError::BufferTooShort) } else { let sa = unsafe { std::ptr::read(&buf[0] as *const _ as _) }; Ok(sa) @@ -83,9 +92,9 @@ fn get_sock(buf: &[u8]) -> Result { } impl SocketAddr { - pub fn parse(buf: &[u8]) -> Result> { + pub fn parse(buf: &[u8]) -> Result { if buf.len() < 2 { - return Err("buffer too short".into()); + return Err(SocketAddrError::BufferTooShort); } let fam = u16::from_ne_bytes(buf[0..2].try_into().unwrap()) as u32; match fam { @@ -166,7 +175,7 @@ impl SocketAddr { cid: sa.svm_cid, })) } - _ => Err(format!("unrecognized socket family {}", fam).into()), + _ => Err(SocketAddrError::UnrecognizedFamily(fam as _)), } } } @@ -176,7 +185,7 @@ mod test { use super::*; #[test] - fn parse_syslog() -> Result<(), Box> { + fn parse_syslog() -> Result<(), SocketAddrError> { // taken from testdata/record-connect-unix-raw.txt let buf = b"\x01\x00\x2F\x64\x65\x76\x2F\x6C\x6F\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; let s = SocketAddr::parse(&buf[..])?; diff --git a/src/types.rs b/src/types.rs index c9c3066..15a570e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; -use std::error::Error as StdError; use std::fmt::{self, Debug, Display}; use std::iter::Iterator; use std::ops::Range; @@ -569,7 +568,7 @@ pub struct RValue<'a> { } impl TryFrom> for Vec { - type Error = Box; + type Error = &'static str; fn try_from(v: RValue) -> Result { match v.value { Value::Str(r, Quote::Braces) => { @@ -589,19 +588,17 @@ impl TryFrom> for Vec { } Ok(sb) } - Value::Number(_) => Err("Won't convert number to string".into()), - Value::List(_) | Value::StringifiedList(_) => { - Err("Can't convert list to scalar".into()) - } - Value::Map(_) => Err("Can't convert map to scalar".into()), - Value::Skipped(_) => Err("Can't convert skipped to scalar".into()), + Value::Number(_) => Err("Won't convert number to string"), + Value::List(_) | Value::StringifiedList(_) => Err("Can't convert list to scalar"), + Value::Map(_) => Err("Can't convert map to scalar"), + Value::Skipped(_) => Err("Can't convert skipped to scalar"), Value::Literal(s) => Ok(s.to_string().into()), } } } impl TryFrom> for Vec> { - type Error = Box; + type Error = &'static str; fn try_from(value: RValue) -> Result { match value.value { Value::List(values) | Value::StringifiedList(values) => { @@ -615,7 +612,7 @@ impl TryFrom> for Vec> { } Ok(rv) } - _ => Err("not a list".into()), + _ => Err("not a list"), } } }