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
30pub struct FmdAdm {
32 handle: *mut fmd_adm_t,
33}
34
35impl FmdAdm {
36 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 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 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 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 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 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 pub fn resources(&self, all: bool) -> Result<Vec<ResourceInfo>, Error> {
150 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub event: Option<NvList>,
557}
558
559#[derive(Debug, Clone)]
560pub struct SerdInfo {
561 pub name: String,
562 pub delta_ns: u64,
564 pub n: u64,
566 pub t_ns: u64,
568 pub count: u32,
570 pub fired: bool,
572}
573
574#[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 Time(u64),
599 Size(u64),
601 String(String),
602 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 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}