/
githubmirror
/
mdevctl
Обзор
Документация
Войти
/
githubmirror
/
mdevctl
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v1.2.0
src/callouts.rs
331 строка
9 KB
Jonathon Jongsma
Report root error when a callout can't be executed
02 дек 2021, 01:51
02 дек 2021, 01:51
ff69f6c
Код
Авторство
О чём код?
use anyhow::{anyhow, Context, Result}; use log::{debug, warn}; use std::ffi::OsStr; use std::fmt::{self, Display, Formatter}; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; use crate::mdev::*; #[derive(Clone, Copy, PartialEq)] pub enum Event { Pre, Post, Notify, Get, } impl Display for Event { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Event::Pre => { write!(f, "pre") } Event::Post => { write!(f, "post") } Event::Notify => { write!(f, "notify") } Event::Get => { write!(f, "get") } } } } #[derive(Clone, Copy)] #[allow(dead_code)] pub enum Action { Start, Stop, Define, Undefine, Modify, Attributes, Test, // used for tests only } impl Display for Action { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Action::Start => write!(f, "start"), Action::Stop => write!(f, "stop"), Action::Define => write!(f, "define"), Action::Undefine => write!(f, "undefine"), Action::Modify => write!(f, "modify"), Action::Attributes => write!(f, "attributes"), Action::Test => write!(f, "test"), } } } #[derive(Clone, Copy)] pub enum State { None, Success, Failure, } impl Display for State { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { State::None => write!(f, "none"), State::Success => write!(f, "success"), State::Failure => write!(f, "failure"), } } } pub struct Callout { state: State, script: Option<PathBuf>, } impl Callout { fn new() -> Callout { Callout { state: State::None, script: None, } } pub fn invoke<F>(dev: &mut MDev, action: Action, func: F) -> Result<()> where F: Fn(&mut MDev) -> Result<()>, { let mut c = Callout::new(); let res = c.callout(dev, Event::Pre, action).and_then(|_| { let tmp_res = func(dev); c.state = match tmp_res { Ok(_) => State::Success, Err(_) => State::Failure, }; let post_res = c.callout(dev, Event::Post, action); if post_res.is_err() { debug!("Error occurred when executing post callout script"); } tmp_res }); let _ = c.notify(dev, action); res } pub fn get_attributes(dev: &mut MDev) -> Result<serde_json::Value> { let event = Event::Get; let action = Action::Attributes; let c = Callout::new(); let dir = dev.env.callout_dir(); if !dir.is_dir() { return Ok(serde_json::Value::Null); } match c.invoke_first_matching_script(dev, dir, event, action) { Some((path, output)) => { if output.status.success() { debug!("Get attributes successfully from callout script"); let mut st = String::from_utf8_lossy(&output.stdout).to_string(); if st.is_empty() { return Ok(serde_json::Value::Null); } if &st == "[{}]" { debug!( "Attribute field for {} is empty", dev.uuid.to_hyphenated().to_string() ); st = "[]".to_string(); } serde_json::from_str(&st) .with_context(|| anyhow!("Unable to parse attributes from JSON")) } else { c.print_err(&output, &path); return Err(anyhow!("failed to get attributes from {:?}", path)); } } None => { debug!( "Device type {} unmatched by callout script", dev.mdev_type.as_ref().unwrap() ); Ok(serde_json::Value::Null) } } } fn invoke_script<P: AsRef<Path>>( &self, dev: &mut MDev, script: P, event: Event, action: Action, ) -> Result<Output> { debug!( "{}-{}: executing {:?}", event, action, script.as_ref().as_os_str() ); let mut cmd = Command::new(script.as_ref().as_os_str()); cmd.arg("-t") .arg(dev.mdev_type()?) .arg("-e") .arg(event.to_string()) .arg("-a") .arg(action.to_string()) .arg("-s") .arg(self.state.to_string()) .arg("-u") .arg(dev.uuid.to_string()) .arg("-p") .arg(dev.parent()?) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let mut child = cmd.spawn()?; if event != Event::Get { let conf = dev.to_json(false)?.to_string(); if let Some(mut child_stdin) = child.stdin.take() { child_stdin .write_all(conf.as_bytes()) .with_context(|| "Failed to write to stdin of command")?; } } child.wait_with_output().map_err(anyhow::Error::from) } fn print_err<P: AsRef<Path>>(&self, output: &Output, script: P) { let sname = script .as_ref() .file_name() .unwrap_or_else(|| OsStr::new("unknown script name")) .to_string_lossy(); let st = String::from_utf8_lossy(&output.stderr); if !st.is_empty() { eprint!("{}: {}", &sname, st); } } fn invoke_first_matching_script<P: AsRef<Path>>( &self, dev: &mut MDev, dir: P, event: Event, action: Action, ) -> Option<(PathBuf, Output)> { debug!( "{}-{}: looking for a matching callout script for dev type '{}'", event, action, dev.mdev_type.as_ref()? ); if dir.as_ref().read_dir().ok()?.count() == 0 { return None; } for s in dir.as_ref().read_dir().ok()? { let path = s.ok()?.path(); match self.invoke_script(dev, &path, event, action) { Ok(res) => { if res.status.code().is_none() { warn!("callout script {:?} was terminated by a signal", path); continue; } else if res.status.code() != Some(2) { debug!("found callout script {:?}", path); return Some((path, res)); } else { debug!( "device type {} unmatched by callout script", dev.mdev_type().ok()? ); } } Err(e) => { debug!("failed to execute callout script {:?}: {:?}", path, e); continue; } } } None } fn callout(&mut self, dev: &mut MDev, event: Event, action: Action) -> Result<()> { let rc = match self.script { Some(ref s) => self .invoke_script(dev, s, event, action) .ok() .and_then(|output| { self.print_err(&output, s); output.status.code() }), _ => { let dir = dev.env.callout_dir(); if !dir.is_dir() { return Ok(()); } self.invoke_first_matching_script(dev, dir, event, action) .and_then(|(path, output)| { self.print_err(&output, &path); self.script = Some(path); output.status.code() }) } }; match rc { Some(0) | None => Ok(()), Some(n) => Err(anyhow!( "callout script {:?} failed with return code {}", self.script.as_ref().unwrap(), n )), } } fn notify(&mut self, dev: &mut MDev, action: Action) -> Result<()> { let event = Event::Notify; let dir = dev.env.notification_dir(); debug!( "{}-{}: executing notification scripts for device {}", event, action, dev.uuid ); if !dir.is_dir() { return Ok(()); } for s in dir.read_dir()? { let path = s?.path(); match self.invoke_script(dev, &path, event, action) { Ok(output) => { if !output.status.success() { debug!("Error occurred when executing notify script {:?}", path); } } _ => { debug!("Failed to execute callout script {:?}", path); continue; } } } Ok(()) } }