pub use illumos_nvpair::{NvList, NvValue};
use std::ffi::{CStr, CString};
use std::os::raw::c_void;
use uuid::Uuid;
use fmd_adm_sys::{
FMD_ADM_MOD_FAILED, FMD_ADM_PROGRAM, FMD_ADM_RSRC_FAULTY, FMD_ADM_RSRC_INVISIBLE,
FMD_ADM_RSRC_UNUSABLE, FMD_ADM_SERD_FIRED, FMD_ADM_VERSION, FMD_TYPE_BOOL, FMD_TYPE_INT32,
FMD_TYPE_INT64, FMD_TYPE_SIZE, FMD_TYPE_STRING, FMD_TYPE_TIME, FMD_TYPE_UINT32,
FMD_TYPE_UINT64, fmd_adm_caseinfo_t, fmd_adm_close, fmd_adm_errmsg, fmd_adm_modinfo_t,
fmd_adm_rsrcinfo_t, fmd_adm_serdinfo_t, fmd_adm_stats_t, fmd_adm_t, fmd_stat_t,
};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("failed to open fmd adm handle")]
Open,
#[error("fmd: {0}")]
Fmd(String),
#[error("interior nul byte in string argument")]
Nul(#[from] std::ffi::NulError),
#[error("invalid UUID from fmd: {0}")]
Uuid(#[from] uuid::Error),
}
pub struct FmdAdm {
handle: *mut fmd_adm_t,
}
unsafe fn cstr_to_owned(p: *const std::os::raw::c_char) -> String {
if p.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
}
impl FmdAdm {
pub fn open() -> Result<Self, Error> {
let handle = unsafe {
fmd_adm_sys::fmd_adm_open(std::ptr::null(), FMD_ADM_PROGRAM, FMD_ADM_VERSION as i32)
};
if handle.is_null() {
return Err(Error::Open);
}
Ok(Self { handle })
}
fn errmsg(&self) -> String {
let p = unsafe { fmd_adm_errmsg(self.handle) };
if p.is_null() {
"unknown error".to_string()
} else {
unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
}
}
pub fn modules(&self) -> Result<Vec<ModuleInfo>, Error> {
let mut results: Vec<ModuleInfo> = Vec::new();
unsafe extern "C" fn callback(
info: *const fmd_adm_modinfo_t,
arg: *mut c_void,
) -> std::os::raw::c_int {
unsafe {
let vec = &mut *(arg as *mut Vec<ModuleInfo>);
let info = &*info;
vec.push(ModuleInfo {
name: cstr_to_owned(info.ami_name),
description: cstr_to_owned(info.ami_desc),
version: cstr_to_owned(info.ami_vers),
failed: (info.ami_flags & FMD_ADM_MOD_FAILED) != 0,
});
0
}
}
let rc = unsafe {
fmd_adm_sys::fmd_adm_module_iter(
self.handle,
Some(callback),
&mut results as *mut _ as *mut c_void,
)
};
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(results)
}
pub fn resources(&self, all: bool) -> Result<Vec<ResourceInfo>, Error> {
struct RawResourceInfo {
fmri: String,
uuid: String,
case: String,
flags: u32,
}
let mut raw: Vec<RawResourceInfo> = Vec::new();
unsafe extern "C" fn callback(
info: *const fmd_adm_rsrcinfo_t,
arg: *mut c_void,
) -> std::os::raw::c_int {
unsafe {
let vec = &mut *(arg as *mut Vec<RawResourceInfo>);
let info = &*info;
vec.push(RawResourceInfo {
fmri: cstr_to_owned(info.ari_fmri),
uuid: cstr_to_owned(info.ari_uuid),
case: cstr_to_owned(info.ari_case),
flags: info.ari_flags,
});
0
}
}
let rc = unsafe {
fmd_adm_sys::fmd_adm_rsrc_iter(
self.handle,
if all { 1 } else { 0 },
Some(callback),
&mut raw as *mut _ as *mut c_void,
)
};
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
raw.into_iter()
.map(|r| {
Ok(ResourceInfo {
fmri: r.fmri,
uuid: r.uuid.parse()?,
case: r.case.parse()?,
faulty: (r.flags & FMD_ADM_RSRC_FAULTY) != 0,
unusable: (r.flags & FMD_ADM_RSRC_UNUSABLE) != 0,
invisible: (r.flags & FMD_ADM_RSRC_INVISIBLE) != 0,
})
})
.collect()
}
pub fn resource_count(&self, all: bool) -> Result<u32, Error> {
let mut count: u32 = 0;
let rc = unsafe {
fmd_adm_sys::fmd_adm_rsrc_count(self.handle, if all { 1 } else { 0 }, &mut count)
};
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(count)
}
pub fn resource_repaired(&mut self, fmri: &str) -> Result<(), Error> {
let fmri = CString::new(fmri)?;
let rc = unsafe { fmd_adm_sys::fmd_adm_rsrc_repaired(self.handle, fmri.as_ptr()) };
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(())
}
pub fn resource_replaced(&mut self, fmri: &str) -> Result<(), Error> {
let fmri = CString::new(fmri)?;
let rc = unsafe { fmd_adm_sys::fmd_adm_rsrc_replaced(self.handle, fmri.as_ptr()) };
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(())
}
pub fn resource_acquit(&mut self, fmri: &str, case_uuid: &Uuid) -> Result<(), Error> {
let fmri = CString::new(fmri)?;
let uuid_c = CString::new(case_uuid.to_string())?;
let rc = unsafe {
fmd_adm_sys::fmd_adm_rsrc_acquit(self.handle, fmri.as_ptr(), uuid_c.as_ptr())
};
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(())
}
pub fn cases(&self, url: Option<&str>) -> Result<Vec<CaseInfo>, Error> {
struct RawCaseInfo {
uuid: String,
code: String,
url: String,
event: Option<NvList>,
}
let mut raw: Vec<RawCaseInfo> = Vec::new();
let url_c = url.map(CString::new).transpose()?;
unsafe extern "C" fn callback(
info: *const fmd_adm_caseinfo_t,
arg: *mut c_void,
) -> std::os::raw::c_int {
unsafe {
let vec = &mut *(arg as *mut Vec<RawCaseInfo>);
let info = &*info;
let event = if info.aci_event.is_null() {
None
} else {
NvList::from_raw(info.aci_event.cast()).ok()
};
vec.push(RawCaseInfo {
uuid: cstr_to_owned(info.aci_uuid),
code: cstr_to_owned(info.aci_code),
url: cstr_to_owned(info.aci_url),
event,
});
0
}
}
let url_ptr = url_c
.as_ref()
.map(|c| c.as_ptr())
.unwrap_or(std::ptr::null());
let rc = unsafe {
fmd_adm_sys::fmd_adm_case_iter(
self.handle,
url_ptr,
Some(callback),
&mut raw as *mut _ as *mut c_void,
)
};
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
raw.into_iter()
.map(|r| {
Ok(CaseInfo {
uuid: r.uuid.parse()?,
code: r.code,
url: r.url,
event: r.event,
})
})
.collect()
}
pub fn case_acquit(&mut self, uuid: &Uuid) -> Result<(), Error> {
let uuid = CString::new(uuid.to_string())?;
let rc = unsafe { fmd_adm_sys::fmd_adm_case_acquit(self.handle, uuid.as_ptr()) };
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(())
}
pub fn serd_engines(&self, module: &str) -> Result<Vec<SerdInfo>, Error> {
let mut results: Vec<SerdInfo> = Vec::new();
let module = CString::new(module)?;
unsafe extern "C" fn callback(
info: *const fmd_adm_serdinfo_t,
arg: *mut c_void,
) -> std::os::raw::c_int {
unsafe {
let vec = &mut *(arg as *mut Vec<SerdInfo>);
let info = &*info;
vec.push(SerdInfo {
name: cstr_to_owned(info.asi_name),
delta_ns: info.asi_delta,
n: info.asi_n,
t_ns: info.asi_t,
count: info.asi_count,
fired: (info.asi_flags & FMD_ADM_SERD_FIRED) != 0,
});
0
}
}
let rc = unsafe {
fmd_adm_sys::fmd_adm_serd_iter(
self.handle,
module.as_ptr(),
Some(callback),
&mut results as *mut _ as *mut c_void,
)
};
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(results)
}
pub fn transports(&self) -> Result<Vec<TransportId>, Error> {
let mut results: Vec<TransportId> = Vec::new();
unsafe extern "C" fn callback(id: fmd_adm_sys::id_t, arg: *mut c_void) {
unsafe {
let vec = &mut *(arg as *mut Vec<TransportId>);
vec.push(TransportId(id));
}
}
let rc = unsafe {
fmd_adm_sys::fmd_adm_xprt_iter(
self.handle,
Some(callback),
&mut results as *mut _ as *mut c_void,
)
};
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
Ok(results)
}
pub fn stats(&self, module: Option<&str>) -> Result<Vec<Stat>, Error> {
let module_c = module.map(CString::new).transpose()?;
let module_ptr = module_c
.as_ref()
.map(|c| c.as_ptr())
.unwrap_or(std::ptr::null());
let mut raw_stats = fmd_adm_stats_t {
ams_buf: std::ptr::null_mut(),
ams_len: 0,
};
let rc =
unsafe { fmd_adm_sys::fmd_adm_stats_read(self.handle, module_ptr, &mut raw_stats) };
if rc != 0 {
return Err(Error::Fmd(self.errmsg()));
}
struct StatsGuard<'a> {
handle: *mut fmd_adm_t,
raw: &'a mut fmd_adm_stats_t,
}
impl Drop for StatsGuard<'_> {
fn drop(&mut self) {
unsafe {
fmd_adm_sys::fmd_adm_stats_free(self.handle, self.raw);
}
}
}
let guard = StatsGuard {
handle: self.handle,
raw: &mut raw_stats,
};
let len = guard.raw.ams_len as usize;
let stats = if len == 0 || guard.raw.ams_buf.is_null() {
Vec::new()
} else {
let slice = unsafe { std::slice::from_raw_parts(guard.raw.ams_buf, len) };
slice.iter().map(|s| unsafe { Stat::from_raw(s) }).collect()
};
drop(guard);
Ok(stats)
}
}
impl Drop for FmdAdm {
fn drop(&mut self) {
unsafe { fmd_adm_close(self.handle) };
}
}
#[derive(Debug, Clone)]
pub struct ModuleInfo {
pub name: String,
pub description: String,
pub version: String,
pub failed: bool,
}
#[derive(Debug, Clone)]
pub struct ResourceInfo {
pub fmri: String,
pub uuid: Uuid,
pub case: Uuid,
pub faulty: bool,
pub unusable: bool,
pub invisible: bool,
}
#[derive(Debug, Clone)]
pub struct CaseInfo {
pub uuid: Uuid,
pub code: String,
pub url: String,
pub event: Option<NvList>,
}
#[derive(Debug, Clone)]
pub struct SerdInfo {
pub name: String,
pub delta_ns: u64,
pub n: u64,
pub t_ns: u64,
pub count: u32,
pub fired: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TransportId(i32);
impl TransportId {
pub fn as_raw(&self) -> i32 {
self.0
}
}
impl std::fmt::Display for TransportId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone)]
pub enum StatValue {
Bool(bool),
Int32(i32),
UInt32(u32),
Int64(i64),
UInt64(u64),
Time(u64),
Size(u64),
String(String),
Unknown {
type_code: u32,
raw: u64,
},
}
impl std::fmt::Display for StatValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StatValue::Bool(v) => write!(f, "{v}"),
StatValue::Int32(v) => write!(f, "{v}"),
StatValue::UInt32(v) => write!(f, "{v}"),
StatValue::Int64(v) => write!(f, "{v}"),
StatValue::UInt64(v) => write!(f, "{v}"),
StatValue::Time(v) => write!(f, "{v}ns"),
StatValue::Size(v) => write!(f, "{v}B"),
StatValue::String(v) => write!(f, "{v}"),
StatValue::Unknown { type_code, raw } => {
write!(f, "unknown(type={type_code}, raw={raw})")
}
}
}
}
#[derive(Debug, Clone)]
pub struct Stat {
pub name: String,
pub description: String,
pub value: StatValue,
}
impl Stat {
unsafe fn from_raw(raw: &fmd_stat_t) -> Self {
let name = unsafe { CStr::from_ptr(raw.fmds_name.as_ptr()) }
.to_string_lossy()
.into_owned();
let description = unsafe { CStr::from_ptr(raw.fmds_desc.as_ptr()) }
.to_string_lossy()
.into_owned();
let value = match raw.fmds_type {
FMD_TYPE_BOOL => StatValue::Bool(unsafe { raw.fmds_value.bool_ } != 0),
FMD_TYPE_INT32 => StatValue::Int32(unsafe { raw.fmds_value.i32_ }),
FMD_TYPE_UINT32 => StatValue::UInt32(unsafe { raw.fmds_value.ui32 }),
FMD_TYPE_INT64 => StatValue::Int64(unsafe { raw.fmds_value.i64_ }),
FMD_TYPE_UINT64 => StatValue::UInt64(unsafe { raw.fmds_value.ui64 }),
FMD_TYPE_TIME => StatValue::Time(unsafe { raw.fmds_value.ui64 }),
FMD_TYPE_SIZE => StatValue::Size(unsafe { raw.fmds_value.ui64 }),
FMD_TYPE_STRING => {
let p = unsafe { raw.fmds_value.str_ };
if p.is_null() {
StatValue::String(String::new())
} else {
StatValue::String(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
}
}
other => StatValue::Unknown {
type_code: other,
raw: unsafe { raw.fmds_value.ui64 },
},
};
Self {
name,
description,
value,
}
}
}