Skip to main content

fmd_adm/
lib.rs

1pub use illumos_nvpair::{NvList, NvValue};
2
3use std::ffi::{CStr, CString};
4use std::os::raw::c_void;
5
6use uuid::Uuid;
7
8use fmd_adm_sys::{
9    fmd_adm_caseinfo_t, fmd_adm_close, fmd_adm_errmsg, fmd_adm_modinfo_t,
10    fmd_adm_rsrcinfo_t, fmd_adm_serdinfo_t, fmd_adm_stats_t, fmd_adm_t,
11    fmd_stat_t, FMD_ADM_MOD_FAILED, FMD_ADM_PROGRAM, FMD_ADM_RSRC_FAULTY,
12    FMD_ADM_RSRC_INVISIBLE, FMD_ADM_RSRC_UNUSABLE, FMD_ADM_SERD_FIRED,
13    FMD_ADM_VERSION, FMD_TYPE_BOOL, FMD_TYPE_INT32, FMD_TYPE_INT64,
14    FMD_TYPE_SIZE, FMD_TYPE_STRING, FMD_TYPE_TIME, FMD_TYPE_UINT32,
15    FMD_TYPE_UINT64,
16};
17
18#[derive(Debug, thiserror::Error)]
19pub enum Error {
20    #[error("failed to open fmd adm handle")]
21    Open,
22    #[error("fmd: {0}")]
23    Fmd(String),
24    #[error("interior nul byte in string argument")]
25    Nul(#[from] std::ffi::NulError),
26    #[error("invalid UUID from fmd: {0}")]
27    Uuid(#[from] uuid::Error),
28}
29
30/// A handle to the Fault Management Daemon administrative interface.
31pub struct FmdAdm {
32    handle: *mut fmd_adm_t,
33}
34
35impl FmdAdm {
36    /// Open a connection to the local fault management daemon.
37    pub fn open() -> Result<Self, Error> {
38        let handle = unsafe {
39            fmd_adm_sys::fmd_adm_open(
40                std::ptr::null(),
41                FMD_ADM_PROGRAM,
42                FMD_ADM_VERSION as i32,
43            )
44        };
45        if handle.is_null() {
46            return Err(Error::Open);
47        }
48        Ok(Self { handle })
49    }
50
51    fn errmsg(&self) -> String {
52        let p = unsafe { fmd_adm_errmsg(self.handle) };
53        if p.is_null() {
54            "unknown error".to_string()
55        } else {
56            unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
57        }
58    }
59
60    /// Iterate over loaded FMD modules.
61    pub fn modules(&self) -> Result<Vec<ModuleInfo>, Error> {
62        let mut results: Vec<ModuleInfo> = Vec::new();
63
64        unsafe extern "C" fn callback(
65            info: *const fmd_adm_modinfo_t,
66            arg: *mut c_void,
67        ) -> std::os::raw::c_int { unsafe {
68            let vec = &mut *(arg as *mut Vec<ModuleInfo>);
69            let info = &*info;
70            vec.push(ModuleInfo {
71                name: CStr::from_ptr(info.ami_name)
72                    .to_string_lossy()
73                    .into_owned(),
74                description: CStr::from_ptr(info.ami_desc)
75                    .to_string_lossy()
76                    .into_owned(),
77                version: CStr::from_ptr(info.ami_vers)
78                    .to_string_lossy()
79                    .into_owned(),
80                failed: (info.ami_flags & FMD_ADM_MOD_FAILED) != 0,
81            });
82            0
83        }}
84
85        let rc = unsafe {
86            fmd_adm_sys::fmd_adm_module_iter(
87                self.handle,
88                Some(callback),
89                &mut results as *mut _ as *mut c_void,
90            )
91        };
92        if rc != 0 {
93            return Err(Error::Fmd(self.errmsg()));
94        }
95        Ok(results)
96    }
97
98    /// Load an FMD module by path.
99    pub fn module_load(&self, path: &str) -> Result<(), Error> {
100        let path = CString::new(path)?;
101        let rc = unsafe {
102            fmd_adm_sys::fmd_adm_module_load(self.handle, path.as_ptr())
103        };
104        if rc != 0 {
105            return Err(Error::Fmd(self.errmsg()));
106        }
107        Ok(())
108    }
109
110    /// Unload an FMD module by name.
111    pub fn module_unload(&self, name: &str) -> Result<(), Error> {
112        let name = CString::new(name)?;
113        let rc = unsafe {
114            fmd_adm_sys::fmd_adm_module_unload(self.handle, name.as_ptr())
115        };
116        if rc != 0 {
117            return Err(Error::Fmd(self.errmsg()));
118        }
119        Ok(())
120    }
121
122    /// Reset an FMD module by name.
123    pub fn module_reset(&self, name: &str) -> Result<(), Error> {
124        let name = CString::new(name)?;
125        let rc = unsafe {
126            fmd_adm_sys::fmd_adm_module_reset(self.handle, name.as_ptr())
127        };
128        if rc != 0 {
129            return Err(Error::Fmd(self.errmsg()));
130        }
131        Ok(())
132    }
133
134    /// Garbage-collect an FMD module by name.
135    pub fn module_gc(&self, name: &str) -> Result<(), Error> {
136        let name = CString::new(name)?;
137        let rc = unsafe {
138            fmd_adm_sys::fmd_adm_module_gc(self.handle, name.as_ptr())
139        };
140        if rc != 0 {
141            return Err(Error::Fmd(self.errmsg()));
142        }
143        Ok(())
144    }
145
146    /// Iterate over faulty resources.
147    ///
148    /// If `all` is true, includes resources that are not directly visible.
149    pub fn resources(&self, all: bool) -> Result<Vec<ResourceInfo>, Error> {
150        // Collect raw strings from the callback, then parse UUIDs
151        // afterwards so we can propagate errors.
152        struct RawResourceInfo {
153            fmri: String,
154            uuid: String,
155            case: String,
156            flags: u32,
157        }
158
159        let mut raw: Vec<RawResourceInfo> = Vec::new();
160
161        unsafe extern "C" fn callback(
162            info: *const fmd_adm_rsrcinfo_t,
163            arg: *mut c_void,
164        ) -> std::os::raw::c_int { unsafe {
165            let vec = &mut *(arg as *mut Vec<RawResourceInfo>);
166            let info = &*info;
167            vec.push(RawResourceInfo {
168                fmri: CStr::from_ptr(info.ari_fmri)
169                    .to_string_lossy()
170                    .into_owned(),
171                uuid: CStr::from_ptr(info.ari_uuid)
172                    .to_string_lossy()
173                    .into_owned(),
174                case: CStr::from_ptr(info.ari_case)
175                    .to_string_lossy()
176                    .into_owned(),
177                flags: info.ari_flags,
178            });
179            0
180        }}
181
182        let rc = unsafe {
183            fmd_adm_sys::fmd_adm_rsrc_iter(
184                self.handle,
185                if all { 1 } else { 0 },
186                Some(callback),
187                &mut raw as *mut _ as *mut c_void,
188            )
189        };
190        if rc != 0 {
191            return Err(Error::Fmd(self.errmsg()));
192        }
193
194        raw.into_iter()
195            .map(|r| {
196                Ok(ResourceInfo {
197                    fmri: r.fmri,
198                    uuid: r.uuid.parse()?,
199                    case: r.case.parse()?,
200                    faulty: (r.flags & FMD_ADM_RSRC_FAULTY) != 0,
201                    unusable: (r.flags & FMD_ADM_RSRC_UNUSABLE) != 0,
202                    invisible: (r.flags & FMD_ADM_RSRC_INVISIBLE) != 0,
203                })
204            })
205            .collect()
206    }
207
208    /// Get the count of faulty resources.
209    pub fn resource_count(&self, all: bool) -> Result<u32, Error> {
210        let mut count: u32 = 0;
211        let rc = unsafe {
212            fmd_adm_sys::fmd_adm_rsrc_count(
213                self.handle,
214                if all { 1 } else { 0 },
215                &mut count,
216            )
217        };
218        if rc != 0 {
219            return Err(Error::Fmd(self.errmsg()));
220        }
221        Ok(count)
222    }
223
224    /// Mark a resource (by FMRI) as repaired.
225    pub fn resource_repaired(&self, fmri: &str) -> Result<(), Error> {
226        let fmri = CString::new(fmri)?;
227        let rc = unsafe {
228            fmd_adm_sys::fmd_adm_rsrc_repaired(self.handle, fmri.as_ptr())
229        };
230        if rc != 0 {
231            return Err(Error::Fmd(self.errmsg()));
232        }
233        Ok(())
234    }
235
236    /// Mark a resource (by FMRI) as replaced.
237    pub fn resource_replaced(&self, fmri: &str) -> Result<(), Error> {
238        let fmri = CString::new(fmri)?;
239        let rc = unsafe {
240            fmd_adm_sys::fmd_adm_rsrc_replaced(self.handle, fmri.as_ptr())
241        };
242        if rc != 0 {
243            return Err(Error::Fmd(self.errmsg()));
244        }
245        Ok(())
246    }
247
248    /// Acquit a resource (by FMRI), optionally specifying a case UUID.
249    ///
250    /// If `case_uuid` is `None`, the resource is acquitted across all cases.
251    pub fn resource_acquit(
252        &self,
253        fmri: &str,
254        case_uuid: Option<&Uuid>,
255    ) -> Result<(), Error> {
256        let fmri = CString::new(fmri)?;
257        let uuid_str = case_uuid.map(|u| CString::new(u.to_string()));
258        let uuid_c = uuid_str.transpose()?;
259        let uuid_ptr = uuid_c
260            .as_ref()
261            .map(|c| c.as_ptr())
262            .unwrap_or(std::ptr::null());
263        let rc = unsafe {
264            fmd_adm_sys::fmd_adm_rsrc_acquit(
265                self.handle,
266                fmri.as_ptr(),
267                uuid_ptr,
268            )
269        };
270        if rc != 0 {
271            return Err(Error::Fmd(self.errmsg()));
272        }
273        Ok(())
274    }
275
276    /// Flush cached state for a resource (by FMRI).
277    pub fn resource_flush(&self, fmri: &str) -> Result<(), Error> {
278        let fmri = CString::new(fmri)?;
279        let rc = unsafe {
280            fmd_adm_sys::fmd_adm_rsrc_flush(self.handle, fmri.as_ptr())
281        };
282        if rc != 0 {
283            return Err(Error::Fmd(self.errmsg()));
284        }
285        Ok(())
286    }
287
288    /// Iterate over cases, optionally filtered by URL.
289    pub fn cases(
290        &self,
291        url: Option<&str>,
292    ) -> Result<Vec<CaseInfo>, Error> {
293        struct RawCaseInfo {
294            uuid: String,
295            code: String,
296            url: String,
297            event: Option<NvList>,
298        }
299
300        let mut raw: Vec<RawCaseInfo> = Vec::new();
301        let url_c = url.map(CString::new).transpose()?;
302
303        unsafe extern "C" fn callback(
304            info: *const fmd_adm_caseinfo_t,
305            arg: *mut c_void,
306        ) -> std::os::raw::c_int { unsafe {
307            let vec = &mut *(arg as *mut Vec<RawCaseInfo>);
308            let info = &*info;
309            let event = if info.aci_event.is_null() {
310                None
311            } else {
312                // If from_raw fails, skip the event rather than
313                // aborting the entire iteration.
314                NvList::from_raw(info.aci_event.cast()).ok()
315            };
316            vec.push(RawCaseInfo {
317                uuid: CStr::from_ptr(info.aci_uuid)
318                    .to_string_lossy()
319                    .into_owned(),
320                code: CStr::from_ptr(info.aci_code)
321                    .to_string_lossy()
322                    .into_owned(),
323                url: CStr::from_ptr(info.aci_url)
324                    .to_string_lossy()
325                    .into_owned(),
326                event,
327            });
328            0
329        }}
330
331        let url_ptr = url_c
332            .as_ref()
333            .map(|c| c.as_ptr())
334            .unwrap_or(std::ptr::null());
335
336        let rc = unsafe {
337            fmd_adm_sys::fmd_adm_case_iter(
338                self.handle,
339                url_ptr,
340                Some(callback),
341                &mut raw as *mut _ as *mut c_void,
342            )
343        };
344        if rc != 0 {
345            return Err(Error::Fmd(self.errmsg()));
346        }
347
348        raw.into_iter()
349            .map(|r| {
350                Ok(CaseInfo {
351                    uuid: r.uuid.parse()?,
352                    code: r.code,
353                    url: r.url,
354                    event: r.event,
355                })
356            })
357            .collect()
358    }
359
360    /// Repair a case by UUID.
361    pub fn case_repair(&self, uuid: &Uuid) -> Result<(), Error> {
362        let uuid = CString::new(uuid.to_string())?;
363        let rc = unsafe {
364            fmd_adm_sys::fmd_adm_case_repair(self.handle, uuid.as_ptr())
365        };
366        if rc != 0 {
367            return Err(Error::Fmd(self.errmsg()));
368        }
369        Ok(())
370    }
371
372    /// Acquit a case by UUID.
373    pub fn case_acquit(&self, uuid: &Uuid) -> Result<(), Error> {
374        let uuid = CString::new(uuid.to_string())?;
375        let rc = unsafe {
376            fmd_adm_sys::fmd_adm_case_acquit(self.handle, uuid.as_ptr())
377        };
378        if rc != 0 {
379            return Err(Error::Fmd(self.errmsg()));
380        }
381        Ok(())
382    }
383
384    /// Iterate over SERD engines for a module.
385    pub fn serd_engines(
386        &self,
387        module: &str,
388    ) -> Result<Vec<SerdInfo>, Error> {
389        let mut results: Vec<SerdInfo> = Vec::new();
390        let module = CString::new(module)?;
391
392        unsafe extern "C" fn callback(
393            info: *const fmd_adm_serdinfo_t,
394            arg: *mut c_void,
395        ) -> std::os::raw::c_int { unsafe {
396            let vec = &mut *(arg as *mut Vec<SerdInfo>);
397            let info = &*info;
398            vec.push(SerdInfo {
399                name: CStr::from_ptr(info.asi_name)
400                    .to_string_lossy()
401                    .into_owned(),
402                delta_ns: info.asi_delta,
403                n: info.asi_n,
404                t_ns: info.asi_t,
405                count: info.asi_count,
406                fired: (info.asi_flags & FMD_ADM_SERD_FIRED) != 0,
407            });
408            0
409        }}
410
411        let rc = unsafe {
412            fmd_adm_sys::fmd_adm_serd_iter(
413                self.handle,
414                module.as_ptr(),
415                Some(callback),
416                &mut results as *mut _ as *mut c_void,
417            )
418        };
419        if rc != 0 {
420            return Err(Error::Fmd(self.errmsg()));
421        }
422        Ok(results)
423    }
424
425    /// Reset a SERD engine.
426    pub fn serd_reset(
427        &self,
428        module: &str,
429        name: &str,
430    ) -> Result<(), Error> {
431        let module = CString::new(module)?;
432        let name = CString::new(name)?;
433        let rc = unsafe {
434            fmd_adm_sys::fmd_adm_serd_reset(
435                self.handle,
436                module.as_ptr(),
437                name.as_ptr(),
438            )
439        };
440        if rc != 0 {
441            return Err(Error::Fmd(self.errmsg()));
442        }
443        Ok(())
444    }
445
446    /// Iterate over transports.
447    pub fn transports(&self) -> Result<Vec<TransportId>, Error> {
448        let mut results: Vec<TransportId> = Vec::new();
449
450        unsafe extern "C" fn callback(
451            id: fmd_adm_sys::id_t,
452            arg: *mut c_void,
453        ) { unsafe {
454            let vec = &mut *(arg as *mut Vec<TransportId>);
455            vec.push(TransportId(id));
456        }}
457
458        let rc = unsafe {
459            fmd_adm_sys::fmd_adm_xprt_iter(
460                self.handle,
461                Some(callback),
462                &mut results as *mut _ as *mut c_void,
463            )
464        };
465        if rc != 0 {
466            return Err(Error::Fmd(self.errmsg()));
467        }
468        Ok(results)
469    }
470
471    /// Read statistics, optionally for a specific module.
472    pub fn stats(
473        &self,
474        module: Option<&str>,
475    ) -> Result<Vec<Stat>, Error> {
476        let module_c = module.map(CString::new).transpose()?;
477        let module_ptr = module_c
478            .as_ref()
479            .map(|c| c.as_ptr())
480            .unwrap_or(std::ptr::null());
481
482        let mut raw_stats = fmd_adm_stats_t {
483            ams_buf: std::ptr::null_mut(),
484            ams_len: 0,
485        };
486
487        let rc = unsafe {
488            fmd_adm_sys::fmd_adm_stats_read(
489                self.handle,
490                module_ptr,
491                &mut raw_stats,
492            )
493        };
494        if rc != 0 {
495            return Err(Error::Fmd(self.errmsg()));
496        }
497
498        let stats = unsafe {
499            let slice = std::slice::from_raw_parts(
500                raw_stats.ams_buf,
501                raw_stats.ams_len as usize,
502            );
503            slice.iter().map(|s| Stat::from_raw(s)).collect()
504        };
505
506        unsafe {
507            fmd_adm_sys::fmd_adm_stats_free(self.handle, &mut raw_stats);
508        }
509
510        Ok(stats)
511    }
512
513    /// Rotate a log file.
514    pub fn log_rotate(&self, log: &str) -> Result<(), Error> {
515        let log = CString::new(log)?;
516        let rc = unsafe {
517            fmd_adm_sys::fmd_adm_log_rotate(self.handle, log.as_ptr())
518        };
519        if rc != 0 {
520            return Err(Error::Fmd(self.errmsg()));
521        }
522        Ok(())
523    }
524}
525
526impl Drop for FmdAdm {
527    fn drop(&mut self) {
528        unsafe { fmd_adm_close(self.handle) };
529    }
530}
531
532#[derive(Debug, Clone)]
533pub struct ModuleInfo {
534    pub name: String,
535    pub description: String,
536    pub version: String,
537    pub failed: bool,
538}
539
540#[derive(Debug, Clone)]
541pub struct ResourceInfo {
542    pub fmri: String,
543    pub uuid: Uuid,
544    pub case: Uuid,
545    pub faulty: bool,
546    pub unusable: bool,
547    pub invisible: bool,
548}
549
550#[derive(Debug, Clone)]
551pub struct CaseInfo {
552    pub uuid: Uuid,
553    pub code: String,
554    pub url: String,
555    /// The full fault event payload as an nvlist, if present.
556    pub event: Option<NvList>,
557}
558
559#[derive(Debug, Clone)]
560pub struct SerdInfo {
561    pub name: String,
562    /// Nanoseconds from oldest event to now.
563    pub delta_ns: u64,
564    /// N parameter (event count threshold).
565    pub n: u64,
566    /// T parameter (nanoseconds window).
567    pub t_ns: u64,
568    /// Number of events currently in engine.
569    pub count: u32,
570    /// Whether the SERD engine has fired.
571    pub fired: bool,
572}
573
574/// An opaque identifier for an FMD transport.
575#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
576pub struct TransportId(i32);
577
578impl TransportId {
579    pub fn as_raw(&self) -> i32 {
580        self.0
581    }
582}
583
584impl std::fmt::Display for TransportId {
585    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586        write!(f, "{}", self.0)
587    }
588}
589
590#[derive(Debug, Clone)]
591pub enum StatValue {
592    Bool(bool),
593    Int32(i32),
594    UInt32(u32),
595    Int64(i64),
596    UInt64(u64),
597    /// A time value in nanoseconds.
598    Time(u64),
599    /// A size value in bytes.
600    Size(u64),
601    String(String),
602    /// A stat type not recognized by this crate.
603    Unknown { type_code: u32, raw: u64 },
604}
605
606impl std::fmt::Display for StatValue {
607    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608        match self {
609            StatValue::Bool(v) => write!(f, "{v}"),
610            StatValue::Int32(v) => write!(f, "{v}"),
611            StatValue::UInt32(v) => write!(f, "{v}"),
612            StatValue::Int64(v) => write!(f, "{v}"),
613            StatValue::UInt64(v) => write!(f, "{v}"),
614            StatValue::Time(v) => write!(f, "{v}ns"),
615            StatValue::Size(v) => write!(f, "{v}B"),
616            StatValue::String(v) => write!(f, "{v}"),
617            StatValue::Unknown { type_code, raw } => {
618                write!(f, "unknown(type={type_code}, raw={raw})")
619            }
620        }
621    }
622}
623
624#[derive(Debug, Clone)]
625pub struct Stat {
626    pub name: String,
627    pub description: String,
628    pub value: StatValue,
629}
630
631impl Stat {
632    /// Convert a raw `fmd_stat_t` into an owned `Stat`.
633    ///
634    /// # Safety
635    /// The `fmd_stat_t` must have been obtained from a valid fmd call.
636    unsafe fn from_raw(raw: &fmd_stat_t) -> Self { unsafe {
637        let name = CStr::from_ptr(raw.fmds_name.as_ptr())
638            .to_string_lossy()
639            .into_owned();
640        let description = CStr::from_ptr(raw.fmds_desc.as_ptr())
641            .to_string_lossy()
642            .into_owned();
643        let value = match raw.fmds_type {
644            FMD_TYPE_BOOL => StatValue::Bool(raw.fmds_value.bool_ != 0),
645            FMD_TYPE_INT32 => StatValue::Int32(raw.fmds_value.i32_),
646            FMD_TYPE_UINT32 => StatValue::UInt32(raw.fmds_value.ui32),
647            FMD_TYPE_INT64 => StatValue::Int64(raw.fmds_value.i64_),
648            FMD_TYPE_UINT64 => StatValue::UInt64(raw.fmds_value.ui64),
649            FMD_TYPE_TIME => StatValue::Time(raw.fmds_value.ui64),
650            FMD_TYPE_SIZE => StatValue::Size(raw.fmds_value.ui64),
651            FMD_TYPE_STRING => {
652                if raw.fmds_value.str_.is_null() {
653                    StatValue::String(String::new())
654                } else {
655                    StatValue::String(
656                        CStr::from_ptr(raw.fmds_value.str_)
657                            .to_string_lossy()
658                            .into_owned(),
659                    )
660                }
661            }
662            other => StatValue::Unknown {
663                type_code: other,
664                raw: raw.fmds_value.ui64,
665            },
666        };
667        Self { name, description, value }
668    }}
669}