use log::{error, warn, info, trace};
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::fmt;
use libc::c_int;
use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
use std::sync::{Arc, Mutex, mpsc::Sender, mpsc::channel, mpsc::RecvTimeoutError};
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
use nix::errno::Errno;
use crate::ioctl::*;
pub enum OptionFlags{
Overheat,
FanFault,
Extern1,
Extern2,
PowerUnder,
CardReset,
PowerOver,
SetTimeout,
MagicClose,
PreTimeout,
AlarmOnly,
KeepalivePing,
}
impl OptionFlags{
fn value(&self) -> u32{
match self{
Self::Overheat => 0x0001,
Self::FanFault => 0x0002,
Self::Extern1 => 0x0004,
Self::Extern2 => 0x0008,
Self::PowerUnder => 0x0010,
Self::CardReset => 0x0020,
Self::PowerOver => 0x0040,
Self::SetTimeout => 0x0080,
Self::MagicClose => 0x0100,
Self::PreTimeout => 0x0200,
Self::AlarmOnly => 0x0400,
Self::KeepalivePing => 0x8000,
}
}
}
impl fmt::Display for OptionFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Overheat => write!(f, "Overheat"),
Self::FanFault => write!(f, "FanFault"),
Self::Extern1 => write!(f, "Extern1"),
Self::Extern2 => write!(f, "Extern2"),
Self::PowerUnder => write!(f, "PowerUnder"),
Self::CardReset => write!(f, "CardReset"),
Self::PowerOver => write!(f, "PowerOver"),
Self::SetTimeout => write!(f, "SetTimeout"),
Self::MagicClose => write!(f, "MagicClose"),
Self::PreTimeout => write!(f, "PreTimeout"),
Self::AlarmOnly => write!(f, "AlarmOnly"),
Self::KeepalivePing => write!(f, "KeepalivePing"),
}
}
}
pub enum SetOptionFlags{
DisableCard,
EnableCard,
TempPanic,
}
impl SetOptionFlags{
fn value(&self) -> u32{
match self{
Self::DisableCard => 0x0001,
Self::EnableCard => 0x0002,
Self::TempPanic => 0x0004,
}
}
}
impl fmt::Display for SetOptionFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::DisableCard => write!(f, "DisableCard"),
Self::EnableCard => write!(f, "EnableCard"),
Self::TempPanic => write!(f, "TempPanic"),
}
}
}
enum BitmaskQueryType{
GetStatus,
GetBootStatus,
}
enum IntGetterType{
GetTimeout,
GetPreTimeout,
GetTimeLeft,
GetTemp,
}
pub struct Watchdog{
file: File,
msg_sender: Option<Sender<()>>,
}
impl Watchdog {
pub fn new() -> Result<Self, io::Error>{
Self::new_instance(None)
}
pub fn new_by_id(id: u8) -> Result<Self, io::Error>{
Self::new_instance(Some(id))
}
fn new_instance(id: Option<u8>) -> Result<Self, io::Error>{
let mut path = String::from("/dev/watchdog");
if let Some(id_val) = id {
path.push_str(&id_val.to_string());
}
let f = OpenOptions::new().write(true).open(&path)?;
warn!("Watchdog:{path} activated.");
Ok(Self{file: f, msg_sender: Option::None})
}
pub fn keep_alive(&mut self) -> Result<(), Errno>{
let result;
unsafe{
result = ioctl_keepalive(self.file.as_raw_fd(), std::ptr::null_mut::<c_int>());
}
match result{
Ok(_) => {
trace!("Keep alive.");
Ok(())
},
Err(e) => Err(e),
}
}
pub fn start_automatic_keep_alive(watchdog_mut_arc: Arc<Mutex<Self>>) -> JoinHandle<()>{
let (tx, rx) = channel::<()>();
watchdog_mut_arc.lock().expect("Couldn't lock the watchdog mutex to set the sender.").msg_sender = Some(tx);
let handle = thread::spawn(move || {
info!("Automatic keepalive thread started.");
let mut keepalive_error_counter = 0;
loop{
if let Err(e) = watchdog_mut_arc.lock().expect("Couldn't lock the watchdog mutex to keep alive.").keep_alive(){
warn!("Keep alive error {}.", e);
keepalive_error_counter += 1;
if keepalive_error_counter >= 10{
error!("Max number of consecutive keepalive errors reached. Closing thread...");
break;
}
}
else{
keepalive_error_counter = 0;
}
if let Err(e) = rx.recv_timeout(Duration::from_secs(1)){
if e == RecvTimeoutError::Timeout{
trace!("timeout 1s...");
}
else{
warn!("Sender was terminated. Closing 'auto keepalive' thread...");
break;
}
} }
info!("Automatic keepalive thread ended.");
});
handle
}
pub fn get_firmware_version(&self) -> Result<u32, Errno> {
#[cfg(unix)]
let mut wd_info: watchdog_info = watchdog_info::new();
let result;
unsafe{
result = ioctl_get_support(self.file.as_raw_fd(),
&mut wd_info as *mut watchdog_info);
}
match result{
Ok(_) => Ok(wd_info.firmware_version),
Err(e) => Err(e),
}
}
fn bitmask_query(&self, option: &OptionFlags, query: &BitmaskQueryType) -> Result<bool, Errno> {
#[cfg(unix)]
let mut bitmask: c_int = -1;
let result;
match query{
BitmaskQueryType::GetStatus =>{
unsafe{
result = ioctl_get_status(self.file.as_raw_fd(),
&mut bitmask as *mut c_int);
}
}
BitmaskQueryType::GetBootStatus =>{
unsafe{
result = ioctl_get_bootstatus(self.file.as_raw_fd(),
&mut bitmask as *mut c_int);
}
}
}
match result{
Ok(_) => {
trace!("bitmask: \n{:#034b}\n{:#034b}",
option.value(),
bitmask);
Ok((bitmask as u32 & option.value()) != 0)
},
Err(e) => Err(e),
}
}
pub fn get_status(&self, option: &OptionFlags) -> Result<bool, Errno> {
self.bitmask_query(option, &BitmaskQueryType::GetStatus)
}
pub fn get_boot_status(&self, option: &OptionFlags) -> Result<bool, Errno> {
self.bitmask_query(option, &BitmaskQueryType::GetBootStatus)
}
pub fn is_option_supported(&self, option: &OptionFlags) -> Result<bool, Errno> {
#[cfg(unix)]
let mut wd_info: watchdog_info = watchdog_info::new();
let result;
unsafe{
result = ioctl_get_support(self.file.as_raw_fd(),
&mut wd_info as *mut watchdog_info);
}
match result{
Ok(_) => {
trace!("options bitmask: \n{:#034b}\n{:#034b}",
option.value(),
wd_info.options);
Ok((wd_info.options & option.value()) != 0)
},
Err(e) => Err(e),
}
}
pub fn get_driver_identity(&self) -> Result<String, Errno> {
#[cfg(unix)]
let mut wd_info: watchdog_info = watchdog_info::new();
let result;
unsafe{
result = ioctl_get_support(self.file.as_raw_fd(),
&mut wd_info as *mut watchdog_info);
}
let string_ident = String::from_utf8_lossy(&wd_info.identity).into_owned();
match result{
Ok(_) => Ok(string_ident),
Err(e) => Err(e),
}
}
fn int_getter(&self, getter_type: IntGetterType) -> Result<i32, Errno> {
#[cfg(unix)]
let mut value: c_int = -1;
let result = match getter_type{
IntGetterType::GetTimeout => unsafe{
ioctl_get_timeout(self.file.as_raw_fd(), &mut value as *mut c_int)
},
IntGetterType::GetPreTimeout => unsafe{
ioctl_get_pretimeout(self.file.as_raw_fd(), &mut value as *mut c_int)
},
IntGetterType::GetTimeLeft => unsafe{
ioctl_get_time_left(self.file.as_raw_fd(), &mut value as *mut c_int)
},
IntGetterType::GetTemp => unsafe{
ioctl_get_temp(self.file.as_raw_fd(), &mut value as *mut c_int)
},
};
match result{
Ok(_) => Ok(value),
Err(e) => Err(e),
}
}
pub fn get_timeout(&self) -> Result<i32, Errno> {
self.int_getter(IntGetterType::GetTimeout)
}
pub fn get_pretimeout(&self) -> Result<i32, Errno> {
self.int_getter(IntGetterType::GetPreTimeout)
}
pub fn get_time_left(&self) -> Result<i32, Errno> {
self.int_getter(IntGetterType::GetTimeLeft)
}
pub fn get_temp(&self) -> Result<i32, Errno> {
self.int_getter(IntGetterType::GetTemp)
}
pub fn set_timeout(&self, timeout: i32) -> Result<i32, Errno> {
#[cfg(unix)]
let mut timeout_for_ioctl: c_int = timeout;
let result;
unsafe{
result = ioctl_set_timeout(self.file.as_raw_fd(),
&mut timeout_for_ioctl as *mut c_int);
}
match result{
Ok(_) => Ok(timeout_for_ioctl),
Err(e) => Err(e),
}
}
pub fn set_pretimeout(&self, pretimeout: i32) -> Result<i32, Errno> {
#[cfg(unix)]
let mut pretimeout_for_ioctl: c_int = pretimeout;
let result;
unsafe{
result = ioctl_set_pretimeout(self.file.as_raw_fd(),
&mut pretimeout_for_ioctl as *mut c_int);
}
match result{
Ok(_) => Ok(pretimeout_for_ioctl),
Err(e) => Err(e),
}
}
pub fn set_option(&self, option: &SetOptionFlags) -> Result<(), Errno> {
#[cfg(unix)]
let mut option_to_set: c_int =
option.value().try_into().expect("option not convertible to c_int");
let result;
unsafe{
result = ioctl_set_options(self.file.as_raw_fd(),
&mut option_to_set as *mut c_int);
}
match result{
Ok(res) => {trace!("Set_option {} returned {}.", option, res); Ok(())},
Err(e) => Err(e),
}
}
pub fn magic_close(&mut self) -> std::io::Result<()>{
if self.msg_sender.is_some(){
self.msg_sender = None;
}
self.file.write_all(b"V")?;
self.file.flush()?;
warn!("Magic close. The watchdog will NOT restart the system.");
Ok(())
}
}
impl Drop for Watchdog {
fn drop(&mut self) {
warn!("Closing watchdog file...");
}
}