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_MOD_FAILED, FMD_ADM_PROGRAM, FMD_ADM_RSRC_FAULTY, FMD_ADM_RSRC_INVISIBLE,
10 FMD_ADM_RSRC_UNUSABLE, FMD_ADM_SERD_FIRED, FMD_ADM_VERSION, FMD_TYPE_BOOL, FMD_TYPE_INT32,
11 FMD_TYPE_INT64, FMD_TYPE_SIZE, FMD_TYPE_STRING, FMD_TYPE_TIME, FMD_TYPE_UINT32,
12 FMD_TYPE_UINT64, fmd_adm_caseinfo_t, fmd_adm_close, fmd_adm_errmsg, fmd_adm_modinfo_t,
13 fmd_adm_rsrcinfo_t, fmd_adm_serdinfo_t, fmd_adm_stats_t, fmd_adm_t, fmd_stat_t,
14};
15
16#[derive(Debug, thiserror::Error)]
17pub enum Error {
18 #[error("failed to open fmd adm handle")]
19 Open,
20 #[error("fmd: {0}")]
21 Fmd(String),
22 #[error("interior nul byte in string argument")]
23 Nul(#[from] std::ffi::NulError),
24 #[error("invalid UUID from fmd: {0}")]
25 Uuid(#[from] uuid::Error),
26}
27
28pub struct FmdAdm {
38 handle: *mut fmd_adm_t,
39}
40
41unsafe fn cstr_to_owned(p: *const std::os::raw::c_char) -> String {
48 if p.is_null() {
49 String::new()
50 } else {
51 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
52 }
53}
54
55impl FmdAdm {
56 pub fn open() -> Result<Self, Error> {
58 let handle = unsafe {
59 fmd_adm_sys::fmd_adm_open(std::ptr::null(), FMD_ADM_PROGRAM, FMD_ADM_VERSION as i32)
60 };
61 if handle.is_null() {
62 return Err(Error::Open);
63 }
64 Ok(Self { handle })
65 }
66
67 fn errmsg(&self) -> String {
68 let p = unsafe { fmd_adm_errmsg(self.handle) };
69 if p.is_null() {
70 "unknown error".to_string()
71 } else {
72 unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
73 }
74 }
75
76 pub fn modules(&self) -> Result<Vec<ModuleInfo>, Error> {
78 let mut results: Vec<ModuleInfo> = Vec::new();
79
80 unsafe extern "C" fn callback(
81 info: *const fmd_adm_modinfo_t,
82 arg: *mut c_void,
83 ) -> std::os::raw::c_int {
84 unsafe {
85 let vec = &mut *(arg as *mut Vec<ModuleInfo>);
86 let info = &*info;
87 vec.push(ModuleInfo {
88 name: cstr_to_owned(info.ami_name),
89 description: cstr_to_owned(info.ami_desc),
90 version: cstr_to_owned(info.ami_vers),
91 failed: (info.ami_flags & FMD_ADM_MOD_FAILED) != 0,
92 });
93 0
94 }
95 }
96
97 let rc = unsafe {
98 fmd_adm_sys::fmd_adm_module_iter(
99 self.handle,
100 Some(callback),
101 &mut results as *mut _ as *mut c_void,
102 )
103 };
104 if rc != 0 {
105 return Err(Error::Fmd(self.errmsg()));
106 }
107 Ok(results)
108 }
109
110 pub fn resources(&self, all: bool) -> Result<Vec<ResourceInfo>, Error> {
114 struct RawResourceInfo {
117 fmri: String,
118 uuid: String,
119 case: String,
120 flags: u32,
121 }
122
123 let mut raw: Vec<RawResourceInfo> = Vec::new();
124
125 unsafe extern "C" fn callback(
126 info: *const fmd_adm_rsrcinfo_t,
127 arg: *mut c_void,
128 ) -> std::os::raw::c_int {
129 unsafe {
130 let vec = &mut *(arg as *mut Vec<RawResourceInfo>);
131 let info = &*info;
132 vec.push(RawResourceInfo {
133 fmri: cstr_to_owned(info.ari_fmri),
134 uuid: cstr_to_owned(info.ari_uuid),
135 case: cstr_to_owned(info.ari_case),
136 flags: info.ari_flags,
137 });
138 0
139 }
140 }
141
142 let rc = unsafe {
143 fmd_adm_sys::fmd_adm_rsrc_iter(
144 self.handle,
145 if all { 1 } else { 0 },
146 Some(callback),
147 &mut raw as *mut _ as *mut c_void,
148 )
149 };
150 if rc != 0 {
151 return Err(Error::Fmd(self.errmsg()));
152 }
153
154 raw.into_iter()
155 .map(|r| {
156 Ok(ResourceInfo {
157 fmri: r.fmri,
158 uuid: r.uuid.parse()?,
159 case: r.case.parse()?,
160 faulty: (r.flags & FMD_ADM_RSRC_FAULTY) != 0,
161 unusable: (r.flags & FMD_ADM_RSRC_UNUSABLE) != 0,
162 invisible: (r.flags & FMD_ADM_RSRC_INVISIBLE) != 0,
163 })
164 })
165 .collect()
166 }
167
168 pub fn resource_count(&self, all: bool) -> Result<u32, Error> {
170 let mut count: u32 = 0;
171 let rc = unsafe {
172 fmd_adm_sys::fmd_adm_rsrc_count(self.handle, if all { 1 } else { 0 }, &mut count)
173 };
174 if rc != 0 {
175 return Err(Error::Fmd(self.errmsg()));
176 }
177 Ok(count)
178 }
179
180 pub fn resource_repaired(&mut self, fmri: &str) -> Result<(), Error> {
182 let fmri = CString::new(fmri)?;
183 let rc = unsafe { fmd_adm_sys::fmd_adm_rsrc_repaired(self.handle, fmri.as_ptr()) };
184 if rc != 0 {
185 return Err(Error::Fmd(self.errmsg()));
186 }
187 Ok(())
188 }
189
190 pub fn resource_replaced(&mut self, fmri: &str) -> Result<(), Error> {
192 let fmri = CString::new(fmri)?;
193 let rc = unsafe { fmd_adm_sys::fmd_adm_rsrc_replaced(self.handle, fmri.as_ptr()) };
194 if rc != 0 {
195 return Err(Error::Fmd(self.errmsg()));
196 }
197 Ok(())
198 }
199
200 pub fn resource_acquit(&mut self, fmri: &str, case_uuid: &Uuid) -> Result<(), Error> {
202 let fmri = CString::new(fmri)?;
203 let uuid_c = CString::new(case_uuid.to_string())?;
204 let rc = unsafe {
205 fmd_adm_sys::fmd_adm_rsrc_acquit(self.handle, fmri.as_ptr(), uuid_c.as_ptr())
206 };
207 if rc != 0 {
208 return Err(Error::Fmd(self.errmsg()));
209 }
210 Ok(())
211 }
212
213 pub fn cases(&self, url: Option<&str>) -> Result<Vec<CaseInfo>, Error> {
215 struct RawCaseInfo {
216 uuid: String,
217 code: String,
218 url: String,
219 event: Option<NvList>,
220 }
221
222 let mut raw: Vec<RawCaseInfo> = Vec::new();
223 let url_c = url.map(CString::new).transpose()?;
224
225 unsafe extern "C" fn callback(
226 info: *const fmd_adm_caseinfo_t,
227 arg: *mut c_void,
228 ) -> std::os::raw::c_int {
229 unsafe {
230 let vec = &mut *(arg as *mut Vec<RawCaseInfo>);
231 let info = &*info;
232 let event = if info.aci_event.is_null() {
233 None
234 } else {
235 NvList::from_raw(info.aci_event.cast()).ok()
241 };
242 vec.push(RawCaseInfo {
243 uuid: cstr_to_owned(info.aci_uuid),
244 code: cstr_to_owned(info.aci_code),
245 url: cstr_to_owned(info.aci_url),
246 event,
247 });
248 0
249 }
250 }
251
252 let url_ptr = url_c
253 .as_ref()
254 .map(|c| c.as_ptr())
255 .unwrap_or(std::ptr::null());
256
257 let rc = unsafe {
258 fmd_adm_sys::fmd_adm_case_iter(
259 self.handle,
260 url_ptr,
261 Some(callback),
262 &mut raw as *mut _ as *mut c_void,
263 )
264 };
265 if rc != 0 {
266 return Err(Error::Fmd(self.errmsg()));
267 }
268
269 raw.into_iter()
270 .map(|r| {
271 Ok(CaseInfo {
272 uuid: r.uuid.parse()?,
273 code: r.code,
274 url: r.url,
275 event: r.event,
276 })
277 })
278 .collect()
279 }
280
281 pub fn case_acquit(&mut self, uuid: &Uuid) -> Result<(), Error> {
283 let uuid = CString::new(uuid.to_string())?;
284 let rc = unsafe { fmd_adm_sys::fmd_adm_case_acquit(self.handle, uuid.as_ptr()) };
285 if rc != 0 {
286 return Err(Error::Fmd(self.errmsg()));
287 }
288 Ok(())
289 }
290
291 pub fn serd_engines(&self, module: &str) -> Result<Vec<SerdInfo>, Error> {
293 let mut results: Vec<SerdInfo> = Vec::new();
294 let module = CString::new(module)?;
295
296 unsafe extern "C" fn callback(
297 info: *const fmd_adm_serdinfo_t,
298 arg: *mut c_void,
299 ) -> std::os::raw::c_int {
300 unsafe {
301 let vec = &mut *(arg as *mut Vec<SerdInfo>);
302 let info = &*info;
303 vec.push(SerdInfo {
304 name: cstr_to_owned(info.asi_name),
305 delta_ns: info.asi_delta,
306 n: info.asi_n,
307 t_ns: info.asi_t,
308 count: info.asi_count,
309 fired: (info.asi_flags & FMD_ADM_SERD_FIRED) != 0,
310 });
311 0
312 }
313 }
314
315 let rc = unsafe {
316 fmd_adm_sys::fmd_adm_serd_iter(
317 self.handle,
318 module.as_ptr(),
319 Some(callback),
320 &mut results as *mut _ as *mut c_void,
321 )
322 };
323 if rc != 0 {
324 return Err(Error::Fmd(self.errmsg()));
325 }
326 Ok(results)
327 }
328
329 pub fn transports(&self) -> Result<Vec<TransportId>, Error> {
331 let mut results: Vec<TransportId> = Vec::new();
332
333 unsafe extern "C" fn callback(id: fmd_adm_sys::id_t, arg: *mut c_void) {
334 unsafe {
335 let vec = &mut *(arg as *mut Vec<TransportId>);
336 vec.push(TransportId(id));
337 }
338 }
339
340 let rc = unsafe {
341 fmd_adm_sys::fmd_adm_xprt_iter(
342 self.handle,
343 Some(callback),
344 &mut results as *mut _ as *mut c_void,
345 )
346 };
347 if rc != 0 {
348 return Err(Error::Fmd(self.errmsg()));
349 }
350 Ok(results)
351 }
352
353 pub fn stats(&self, module: Option<&str>) -> Result<Vec<Stat>, Error> {
355 let module_c = module.map(CString::new).transpose()?;
356 let module_ptr = module_c
357 .as_ref()
358 .map(|c| c.as_ptr())
359 .unwrap_or(std::ptr::null());
360
361 let mut raw_stats = fmd_adm_stats_t {
362 ams_buf: std::ptr::null_mut(),
363 ams_len: 0,
364 };
365
366 let rc =
367 unsafe { fmd_adm_sys::fmd_adm_stats_read(self.handle, module_ptr, &mut raw_stats) };
368 if rc != 0 {
369 return Err(Error::Fmd(self.errmsg()));
370 }
371
372 struct StatsGuard<'a> {
374 handle: *mut fmd_adm_t,
375 raw: &'a mut fmd_adm_stats_t,
376 }
377 impl Drop for StatsGuard<'_> {
378 fn drop(&mut self) {
379 unsafe {
380 fmd_adm_sys::fmd_adm_stats_free(self.handle, self.raw);
381 }
382 }
383 }
384 let guard = StatsGuard {
385 handle: self.handle,
386 raw: &mut raw_stats,
387 };
388
389 let len = guard.raw.ams_len as usize;
390 let stats = if len == 0 || guard.raw.ams_buf.is_null() {
391 Vec::new()
392 } else {
393 let slice = unsafe { std::slice::from_raw_parts(guard.raw.ams_buf, len) };
394 slice.iter().map(|s| unsafe { Stat::from_raw(s) }).collect()
395 };
396
397 drop(guard);
399
400 Ok(stats)
401 }
402}
403
404impl Drop for FmdAdm {
405 fn drop(&mut self) {
406 unsafe { fmd_adm_close(self.handle) };
407 }
408}
409
410#[derive(Debug, Clone)]
411pub struct ModuleInfo {
412 pub name: String,
413 pub description: String,
414 pub version: String,
415 pub failed: bool,
416}
417
418#[derive(Debug, Clone)]
419pub struct ResourceInfo {
420 pub fmri: String,
421 pub uuid: Uuid,
422 pub case: Uuid,
423 pub faulty: bool,
424 pub unusable: bool,
425 pub invisible: bool,
426}
427
428#[derive(Debug, Clone)]
429pub struct CaseInfo {
430 pub uuid: Uuid,
431 pub code: String,
432 pub url: String,
433 pub event: Option<NvList>,
435}
436
437#[derive(Debug, Clone)]
438pub struct SerdInfo {
439 pub name: String,
440 pub delta_ns: u64,
442 pub n: u64,
444 pub t_ns: u64,
446 pub count: u32,
448 pub fired: bool,
450}
451
452#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
454pub struct TransportId(i32);
455
456impl TransportId {
457 pub fn as_raw(&self) -> i32 {
458 self.0
459 }
460}
461
462impl std::fmt::Display for TransportId {
463 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
464 write!(f, "{}", self.0)
465 }
466}
467
468#[derive(Debug, Clone)]
469pub enum StatValue {
470 Bool(bool),
471 Int32(i32),
472 UInt32(u32),
473 Int64(i64),
474 UInt64(u64),
475 Time(u64),
477 Size(u64),
479 String(String),
480 Unknown {
482 type_code: u32,
483 raw: u64,
484 },
485}
486
487impl std::fmt::Display for StatValue {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 match self {
490 StatValue::Bool(v) => write!(f, "{v}"),
491 StatValue::Int32(v) => write!(f, "{v}"),
492 StatValue::UInt32(v) => write!(f, "{v}"),
493 StatValue::Int64(v) => write!(f, "{v}"),
494 StatValue::UInt64(v) => write!(f, "{v}"),
495 StatValue::Time(v) => write!(f, "{v}ns"),
496 StatValue::Size(v) => write!(f, "{v}B"),
497 StatValue::String(v) => write!(f, "{v}"),
498 StatValue::Unknown { type_code, raw } => {
499 write!(f, "unknown(type={type_code}, raw={raw})")
500 }
501 }
502 }
503}
504
505#[derive(Debug, Clone)]
506pub struct Stat {
507 pub name: String,
508 pub description: String,
509 pub value: StatValue,
510}
511
512impl Stat {
513 unsafe fn from_raw(raw: &fmd_stat_t) -> Self {
518 let name = unsafe { CStr::from_ptr(raw.fmds_name.as_ptr()) }
519 .to_string_lossy()
520 .into_owned();
521 let description = unsafe { CStr::from_ptr(raw.fmds_desc.as_ptr()) }
522 .to_string_lossy()
523 .into_owned();
524 let value = match raw.fmds_type {
525 FMD_TYPE_BOOL => StatValue::Bool(unsafe { raw.fmds_value.bool_ } != 0),
526 FMD_TYPE_INT32 => StatValue::Int32(unsafe { raw.fmds_value.i32_ }),
527 FMD_TYPE_UINT32 => StatValue::UInt32(unsafe { raw.fmds_value.ui32 }),
528 FMD_TYPE_INT64 => StatValue::Int64(unsafe { raw.fmds_value.i64_ }),
529 FMD_TYPE_UINT64 => StatValue::UInt64(unsafe { raw.fmds_value.ui64 }),
530 FMD_TYPE_TIME => StatValue::Time(unsafe { raw.fmds_value.ui64 }),
531 FMD_TYPE_SIZE => StatValue::Size(unsafe { raw.fmds_value.ui64 }),
532 FMD_TYPE_STRING => {
533 let p = unsafe { raw.fmds_value.str_ };
534 if p.is_null() {
535 StatValue::String(String::new())
536 } else {
537 StatValue::String(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
538 }
539 }
540 other => StatValue::Unknown {
541 type_code: other,
542 raw: unsafe { raw.fmds_value.ui64 },
543 },
544 };
545 Self {
546 name,
547 description,
548 value,
549 }
550 }
551}