Skip to main content

illumos_nvpair/
lib.rs

1#![allow(non_upper_case_globals)]
2
3//! Idiomatic Rust representation of illumos nvlists.
4//!
5//! This crate converts raw `nvlist_t` pointers (from libnvpair) into
6//! pure Rust types with no raw pointers or FFI in the public API.
7//!
8//! # Example
9//!
10//! ```ignore
11//! use illumos_nvpair::{NvList, NvValue};
12//!
13//! // Given a raw nvlist pointer from some illumos API:
14//! let nvl = unsafe { NvList::from_raw(raw_ptr) }?;
15//!
16//! if let Some(NvValue::String(s)) = nvl.lookup("name") {
17//!     println!("name = {s}");
18//! }
19//! ```
20
21use std::ffi::CStr;
22use std::fmt;
23use std::os::raw::c_char;
24
25use illumos_nvpair_sys::{
26    data_type_t, data_type_t_DATA_TYPE_BOOLEAN, data_type_t_DATA_TYPE_BOOLEAN_ARRAY,
27    data_type_t_DATA_TYPE_BOOLEAN_VALUE, data_type_t_DATA_TYPE_BYTE,
28    data_type_t_DATA_TYPE_BYTE_ARRAY, data_type_t_DATA_TYPE_DOUBLE, data_type_t_DATA_TYPE_HRTIME,
29    data_type_t_DATA_TYPE_INT8, data_type_t_DATA_TYPE_INT8_ARRAY, data_type_t_DATA_TYPE_INT16,
30    data_type_t_DATA_TYPE_INT16_ARRAY, data_type_t_DATA_TYPE_INT32,
31    data_type_t_DATA_TYPE_INT32_ARRAY, data_type_t_DATA_TYPE_INT64,
32    data_type_t_DATA_TYPE_INT64_ARRAY, data_type_t_DATA_TYPE_NVLIST,
33    data_type_t_DATA_TYPE_NVLIST_ARRAY, data_type_t_DATA_TYPE_STRING,
34    data_type_t_DATA_TYPE_STRING_ARRAY, data_type_t_DATA_TYPE_UINT8,
35    data_type_t_DATA_TYPE_UINT8_ARRAY, data_type_t_DATA_TYPE_UINT16,
36    data_type_t_DATA_TYPE_UINT16_ARRAY, data_type_t_DATA_TYPE_UINT32,
37    data_type_t_DATA_TYPE_UINT32_ARRAY, data_type_t_DATA_TYPE_UINT64,
38    data_type_t_DATA_TYPE_UINT64_ARRAY, nvlist_next_nvpair, nvlist_t, nvpair_name, nvpair_t,
39    nvpair_type, nvpair_value_boolean_array, nvpair_value_boolean_value, nvpair_value_byte,
40    nvpair_value_byte_array, nvpair_value_double, nvpair_value_hrtime, nvpair_value_int8,
41    nvpair_value_int8_array, nvpair_value_int16, nvpair_value_int16_array, nvpair_value_int32,
42    nvpair_value_int32_array, nvpair_value_int64, nvpair_value_int64_array, nvpair_value_nvlist,
43    nvpair_value_nvlist_array, nvpair_value_string, nvpair_value_string_array, nvpair_value_uint8,
44    nvpair_value_uint8_array, nvpair_value_uint16, nvpair_value_uint16_array, nvpair_value_uint32,
45    nvpair_value_uint32_array, nvpair_value_uint64, nvpair_value_uint64_array, uint_t,
46};
47
48/// Error returned when reading an nvpair value fails.
49#[derive(Debug, Clone, PartialEq)]
50pub enum NvError {
51    /// A `nvpair_value_*` C function returned a non-zero error code.
52    ValueReadFailed {
53        pair_name: String,
54        type_code: data_type_t,
55        errno: i32,
56    },
57    /// A C function returned a null pointer where a valid one was required.
58    NullPointer {
59        pair_name: String,
60        type_code: data_type_t,
61    },
62    /// `nvpair_name` returned a null pointer for a pair.
63    NullName,
64}
65
66impl fmt::Display for NvError {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        match self {
69            NvError::ValueReadFailed {
70                pair_name,
71                type_code,
72                errno,
73            } => {
74                write!(
75                    f,
76                    "nvpair_value failed for pair {:?} (type {type_code}): errno {errno}",
77                    pair_name,
78                )
79            }
80            NvError::NullPointer {
81                pair_name,
82                type_code,
83            } => {
84                write!(
85                    f,
86                    "nvpair_value returned null for pair {:?} (type {type_code})",
87                    pair_name,
88                )
89            }
90            NvError::NullName => {
91                write!(f, "nvpair_name returned null")
92            }
93        }
94    }
95}
96
97impl std::error::Error for NvError {}
98
99/// An ordered list of name-value pairs, converted from an illumos nvlist.
100#[derive(Debug, Clone, PartialEq)]
101pub struct NvList {
102    pairs: Vec<(String, NvValue)>,
103}
104
105impl NvList {
106    /// Convert a raw `nvlist_t *` into an owned [`NvList`].
107    ///
108    /// Pair names and string values are converted from C strings using
109    /// lossy UTF-8 conversion: any bytes that are not valid UTF-8 are
110    /// replaced with U+FFFD (`\u{FFFD}`). This means a `lookup()` call
111    /// will not match the original name if it contained non-UTF-8 bytes.
112    ///
113    /// # Safety
114    ///
115    /// `nvl` must be a valid, non-null pointer to an nvlist. The nvlist is
116    /// borrowed - the caller retains ownership and is responsible for
117    /// freeing it.
118    pub unsafe fn from_raw(nvl: *mut nvlist_t) -> Result<NvList, NvError> {
119        let mut pairs = Vec::new();
120        let mut nvp: *mut nvpair_t = std::ptr::null_mut();
121
122        loop {
123            nvp = unsafe { nvlist_next_nvpair(nvl, nvp) };
124            if nvp.is_null() {
125                break;
126            }
127
128            let name_ptr = unsafe { nvpair_name(nvp) };
129            if name_ptr.is_null() {
130                return Err(NvError::NullName);
131            }
132            let name = unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() };
133            let dtype = unsafe { nvpair_type(nvp) };
134            let value = unsafe { read_pair_value(nvp, &name, dtype)? };
135            pairs.push((name, value));
136        }
137
138        Ok(NvList { pairs })
139    }
140
141    /// Look up a value by name.
142    pub fn lookup(&self, name: &str) -> Option<&NvValue> {
143        self.pairs.iter().find(|(n, _)| n == name).map(|(_, v)| v)
144    }
145
146    /// Returns an iterator over the name-value pairs.
147    pub fn iter(&self) -> impl Iterator<Item = (&str, &NvValue)> {
148        self.pairs.iter().map(|(n, v)| (n.as_str(), v))
149    }
150
151    /// Returns the number of pairs.
152    pub fn len(&self) -> usize {
153        self.pairs.len()
154    }
155
156    /// Returns `true` if the list contains no pairs.
157    pub fn is_empty(&self) -> bool {
158        self.pairs.is_empty()
159    }
160}
161
162impl IntoIterator for NvList {
163    type Item = (String, NvValue);
164    type IntoIter = std::vec::IntoIter<(String, NvValue)>;
165
166    fn into_iter(self) -> Self::IntoIter {
167        self.pairs.into_iter()
168    }
169}
170
171impl<'a> IntoIterator for &'a NvList {
172    type Item = (&'a str, &'a NvValue);
173    type IntoIter = std::iter::Map<
174        std::slice::Iter<'a, (String, NvValue)>,
175        fn(&'a (String, NvValue)) -> (&'a str, &'a NvValue),
176    >;
177
178    fn into_iter(self) -> Self::IntoIter {
179        self.pairs.iter().map(|(n, v)| (n.as_str(), v))
180    }
181}
182
183/// A value from an nvlist pair.
184#[derive(Debug, Clone, PartialEq)]
185pub enum NvValue {
186    /// A valueless boolean (presence indicates true).
187    Boolean,
188    /// A boolean with an explicit value.
189    BooleanValue(bool),
190    Byte(u8),
191    Int8(i8),
192    UInt8(u8),
193    Int16(i16),
194    UInt16(u16),
195    Int32(i32),
196    UInt32(u32),
197    Int64(i64),
198    UInt64(u64),
199    Double(f64),
200    String(String),
201    /// High-resolution time in nanoseconds.
202    Hrtime(i64),
203    NvList(NvList),
204    BooleanArray(Vec<bool>),
205    ByteArray(Vec<u8>),
206    Int8Array(Vec<i8>),
207    UInt8Array(Vec<u8>),
208    Int16Array(Vec<i16>),
209    UInt16Array(Vec<u16>),
210    Int32Array(Vec<i32>),
211    UInt32Array(Vec<u32>),
212    Int64Array(Vec<i64>),
213    UInt64Array(Vec<u64>),
214    StringArray(Vec<String>),
215    NvListArray(Vec<NvList>),
216    /// A type not recognized by this crate.
217    Unknown {
218        type_code: data_type_t,
219    },
220}
221
222
223/// Check a return code from a `nvpair_value_*` call, converting
224/// non-zero into an `NvError`.
225fn check_rc(rc: i32, pair_name: &str, type_code: data_type_t) -> Result<(), NvError> {
226    if rc != 0 {
227        Err(NvError::ValueReadFailed {
228            pair_name: pair_name.to_owned(),
229            type_code,
230            errno: rc,
231        })
232    } else {
233        Ok(())
234    }
235}
236
237/// Safely convert a C array pointer + count into a `Vec<T>`.
238/// Returns an empty `Vec` when `n == 0` (avoiding `from_raw_parts`
239/// on a potentially null pointer). Returns `NvError::NullPointer`
240/// if `n > 0` but the pointer is null.
241///
242/// # Safety
243///
244/// When `n > 0`, `p` must point to `n` valid, aligned, initialised
245/// elements of `T`.
246unsafe fn array_to_vec<T: Clone>(
247    p: *const T,
248    n: uint_t,
249    pair_name: &str,
250    type_code: data_type_t,
251) -> Result<Vec<T>, NvError> {
252    unsafe {
253        let len = n as usize;
254        if len == 0 {
255            return Ok(Vec::new());
256        }
257        if p.is_null() {
258            return Err(NvError::NullPointer {
259                pair_name: pair_name.to_owned(),
260                type_code,
261            });
262        }
263        Ok(std::slice::from_raw_parts(p, len).to_vec())
264    }
265}
266
267unsafe fn read_pair_value(
268    nvp: *mut nvpair_t,
269    pair_name: &str,
270    dtype: data_type_t,
271) -> Result<NvValue, NvError> {
272    unsafe {
273        match dtype {
274            data_type_t_DATA_TYPE_BOOLEAN => Ok(NvValue::Boolean),
275            data_type_t_DATA_TYPE_BOOLEAN_VALUE => {
276                let mut v: illumos_nvpair_sys::boolean_t = 0;
277                check_rc(nvpair_value_boolean_value(nvp, &mut v), pair_name, dtype)?;
278                Ok(NvValue::BooleanValue(v != 0))
279            }
280            data_type_t_DATA_TYPE_BYTE => {
281                let mut v: illumos_nvpair_sys::uchar_t = 0;
282                check_rc(nvpair_value_byte(nvp, &mut v), pair_name, dtype)?;
283                Ok(NvValue::Byte(v))
284            }
285            data_type_t_DATA_TYPE_INT8 => {
286                let mut v: i8 = 0;
287                check_rc(nvpair_value_int8(nvp, &mut v), pair_name, dtype)?;
288                Ok(NvValue::Int8(v))
289            }
290            data_type_t_DATA_TYPE_UINT8 => {
291                let mut v: u8 = 0;
292                check_rc(nvpair_value_uint8(nvp, &mut v), pair_name, dtype)?;
293                Ok(NvValue::UInt8(v))
294            }
295            data_type_t_DATA_TYPE_INT16 => {
296                let mut v: i16 = 0;
297                check_rc(nvpair_value_int16(nvp, &mut v), pair_name, dtype)?;
298                Ok(NvValue::Int16(v))
299            }
300            data_type_t_DATA_TYPE_UINT16 => {
301                let mut v: u16 = 0;
302                check_rc(nvpair_value_uint16(nvp, &mut v), pair_name, dtype)?;
303                Ok(NvValue::UInt16(v))
304            }
305            data_type_t_DATA_TYPE_INT32 => {
306                let mut v: i32 = 0;
307                check_rc(nvpair_value_int32(nvp, &mut v), pair_name, dtype)?;
308                Ok(NvValue::Int32(v))
309            }
310            data_type_t_DATA_TYPE_UINT32 => {
311                let mut v: u32 = 0;
312                check_rc(nvpair_value_uint32(nvp, &mut v), pair_name, dtype)?;
313                Ok(NvValue::UInt32(v))
314            }
315            data_type_t_DATA_TYPE_INT64 => {
316                let mut v: i64 = 0;
317                check_rc(nvpair_value_int64(nvp, &mut v), pair_name, dtype)?;
318                Ok(NvValue::Int64(v))
319            }
320            data_type_t_DATA_TYPE_UINT64 => {
321                let mut v: u64 = 0;
322                check_rc(nvpair_value_uint64(nvp, &mut v), pair_name, dtype)?;
323                Ok(NvValue::UInt64(v))
324            }
325            data_type_t_DATA_TYPE_DOUBLE => {
326                let mut v: f64 = 0.0;
327                check_rc(nvpair_value_double(nvp, &mut v), pair_name, dtype)?;
328                Ok(NvValue::Double(v))
329            }
330            data_type_t_DATA_TYPE_STRING => {
331                let mut p: *mut c_char = std::ptr::null_mut();
332                check_rc(nvpair_value_string(nvp, &mut p), pair_name, dtype)?;
333                if p.is_null() {
334                    return Err(NvError::NullPointer {
335                        pair_name: pair_name.to_owned(),
336                        type_code: dtype,
337                    });
338                }
339                let s = CStr::from_ptr(p).to_string_lossy().into_owned();
340                Ok(NvValue::String(s))
341            }
342            data_type_t_DATA_TYPE_HRTIME => {
343                let mut v: illumos_nvpair_sys::hrtime_t = 0;
344                check_rc(nvpair_value_hrtime(nvp, &mut v), pair_name, dtype)?;
345                Ok(NvValue::Hrtime(v))
346            }
347            data_type_t_DATA_TYPE_NVLIST => {
348                let mut p: *mut nvlist_t = std::ptr::null_mut();
349                check_rc(nvpair_value_nvlist(nvp, &mut p), pair_name, dtype)?;
350                if p.is_null() {
351                    return Err(NvError::NullPointer {
352                        pair_name: pair_name.to_owned(),
353                        type_code: dtype,
354                    });
355                }
356                Ok(NvValue::NvList(NvList::from_raw(p)?))
357            }
358            data_type_t_DATA_TYPE_BOOLEAN_ARRAY => {
359                let mut p: *mut illumos_nvpair_sys::boolean_t = std::ptr::null_mut();
360                let mut n: uint_t = 0;
361                check_rc(
362                    nvpair_value_boolean_array(nvp, &mut p, &mut n),
363                    pair_name,
364                    dtype,
365                )?;
366                let raw = array_to_vec(p, n, pair_name, dtype)?;
367                Ok(NvValue::BooleanArray(raw.iter().map(|&v| v != 0).collect()))
368            }
369            data_type_t_DATA_TYPE_BYTE_ARRAY => {
370                let mut p: *mut illumos_nvpair_sys::uchar_t = std::ptr::null_mut();
371                let mut n: uint_t = 0;
372                check_rc(
373                    nvpair_value_byte_array(nvp, &mut p, &mut n),
374                    pair_name,
375                    dtype,
376                )?;
377                Ok(NvValue::ByteArray(array_to_vec(p, n, pair_name, dtype)?))
378            }
379            data_type_t_DATA_TYPE_INT8_ARRAY => {
380                let mut p: *mut i8 = std::ptr::null_mut();
381                let mut n: uint_t = 0;
382                check_rc(
383                    nvpair_value_int8_array(nvp, &mut p, &mut n),
384                    pair_name,
385                    dtype,
386                )?;
387                Ok(NvValue::Int8Array(array_to_vec(p, n, pair_name, dtype)?))
388            }
389            data_type_t_DATA_TYPE_UINT8_ARRAY => {
390                let mut p: *mut u8 = std::ptr::null_mut();
391                let mut n: uint_t = 0;
392                check_rc(
393                    nvpair_value_uint8_array(nvp, &mut p, &mut n),
394                    pair_name,
395                    dtype,
396                )?;
397                Ok(NvValue::UInt8Array(array_to_vec(p, n, pair_name, dtype)?))
398            }
399            data_type_t_DATA_TYPE_INT16_ARRAY => {
400                let mut p: *mut i16 = std::ptr::null_mut();
401                let mut n: uint_t = 0;
402                check_rc(
403                    nvpair_value_int16_array(nvp, &mut p, &mut n),
404                    pair_name,
405                    dtype,
406                )?;
407                Ok(NvValue::Int16Array(array_to_vec(p, n, pair_name, dtype)?))
408            }
409            data_type_t_DATA_TYPE_UINT16_ARRAY => {
410                let mut p: *mut u16 = std::ptr::null_mut();
411                let mut n: uint_t = 0;
412                check_rc(
413                    nvpair_value_uint16_array(nvp, &mut p, &mut n),
414                    pair_name,
415                    dtype,
416                )?;
417                Ok(NvValue::UInt16Array(array_to_vec(p, n, pair_name, dtype)?))
418            }
419            data_type_t_DATA_TYPE_INT32_ARRAY => {
420                let mut p: *mut i32 = std::ptr::null_mut();
421                let mut n: uint_t = 0;
422                check_rc(
423                    nvpair_value_int32_array(nvp, &mut p, &mut n),
424                    pair_name,
425                    dtype,
426                )?;
427                Ok(NvValue::Int32Array(array_to_vec(p, n, pair_name, dtype)?))
428            }
429            data_type_t_DATA_TYPE_UINT32_ARRAY => {
430                let mut p: *mut u32 = std::ptr::null_mut();
431                let mut n: uint_t = 0;
432                check_rc(
433                    nvpair_value_uint32_array(nvp, &mut p, &mut n),
434                    pair_name,
435                    dtype,
436                )?;
437                Ok(NvValue::UInt32Array(array_to_vec(p, n, pair_name, dtype)?))
438            }
439            data_type_t_DATA_TYPE_INT64_ARRAY => {
440                let mut p: *mut i64 = std::ptr::null_mut();
441                let mut n: uint_t = 0;
442                check_rc(
443                    nvpair_value_int64_array(nvp, &mut p, &mut n),
444                    pair_name,
445                    dtype,
446                )?;
447                Ok(NvValue::Int64Array(array_to_vec(p, n, pair_name, dtype)?))
448            }
449            data_type_t_DATA_TYPE_UINT64_ARRAY => {
450                let mut p: *mut u64 = std::ptr::null_mut();
451                let mut n: uint_t = 0;
452                check_rc(
453                    nvpair_value_uint64_array(nvp, &mut p, &mut n),
454                    pair_name,
455                    dtype,
456                )?;
457                Ok(NvValue::UInt64Array(array_to_vec(p, n, pair_name, dtype)?))
458            }
459            data_type_t_DATA_TYPE_STRING_ARRAY => {
460                let mut p: *mut *mut c_char = std::ptr::null_mut();
461                let mut n: uint_t = 0;
462                check_rc(
463                    nvpair_value_string_array(nvp, &mut p, &mut n),
464                    pair_name,
465                    dtype,
466                )?;
467                let ptrs = array_to_vec(p as *const *mut c_char, n, pair_name, dtype)?;
468                let mut strings = Vec::with_capacity(ptrs.len());
469                for &s in &ptrs {
470                    if s.is_null() {
471                        return Err(NvError::NullPointer {
472                            pair_name: pair_name.to_owned(),
473                            type_code: dtype,
474                        });
475                    }
476                    strings.push(CStr::from_ptr(s).to_string_lossy().into_owned());
477                }
478                Ok(NvValue::StringArray(strings))
479            }
480            data_type_t_DATA_TYPE_NVLIST_ARRAY => {
481                let mut p: *mut *mut nvlist_t = std::ptr::null_mut();
482                let mut n: uint_t = 0;
483                check_rc(
484                    nvpair_value_nvlist_array(nvp, &mut p, &mut n),
485                    pair_name,
486                    dtype,
487                )?;
488                let ptrs = array_to_vec(p as *const *mut nvlist_t, n, pair_name, dtype)?;
489                let mut lists = Vec::with_capacity(ptrs.len());
490                for &nvl in &ptrs {
491                    if nvl.is_null() {
492                        return Err(NvError::NullPointer {
493                            pair_name: pair_name.to_owned(),
494                            type_code: dtype,
495                        });
496                    }
497                    lists.push(NvList::from_raw(nvl)?);
498                }
499                Ok(NvValue::NvListArray(lists))
500            }
501            other => Ok(NvValue::Unknown { type_code: other }),
502        }
503    }
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509    use illumos_nvpair_sys::{
510        NV_UNIQUE_NAME, boolean_t, nvlist_add_boolean, nvlist_add_boolean_array,
511        nvlist_add_boolean_value, nvlist_add_byte, nvlist_add_byte_array, nvlist_add_double,
512        nvlist_add_hrtime, nvlist_add_int8, nvlist_add_int8_array, nvlist_add_int16,
513        nvlist_add_int16_array, nvlist_add_int32, nvlist_add_int32_array, nvlist_add_int64,
514        nvlist_add_int64_array, nvlist_add_nvlist, nvlist_add_nvlist_array, nvlist_add_string,
515        nvlist_add_string_array, nvlist_add_uint8, nvlist_add_uint8_array, nvlist_add_uint16,
516        nvlist_add_uint16_array, nvlist_add_uint32, nvlist_add_uint32_array, nvlist_add_uint64,
517        nvlist_add_uint64_array, nvlist_alloc, nvlist_free,
518    };
519    use std::ffi::CString;
520
521    /// RAII wrapper around a C nvlist for test construction.
522    struct NvListBuilder {
523        ptr: *mut nvlist_t,
524    }
525
526    impl NvListBuilder {
527        fn new() -> Self {
528            let mut ptr: *mut nvlist_t = std::ptr::null_mut();
529            let rc = unsafe { nvlist_alloc(&mut ptr, NV_UNIQUE_NAME, 0) };
530            assert_eq!(rc, 0, "nvlist_alloc failed");
531            assert!(!ptr.is_null());
532            NvListBuilder { ptr }
533        }
534
535        fn to_rust(&self) -> NvList {
536            unsafe { NvList::from_raw(self.ptr) }.expect("nvlist_to_rust failed")
537        }
538
539        fn add_boolean(&self, name: &str) -> &Self {
540            let cname = CString::new(name).unwrap();
541            let rc = unsafe { nvlist_add_boolean(self.ptr, cname.as_ptr()) };
542            assert_eq!(rc, 0, "nvlist_add_boolean failed for {name}");
543            self
544        }
545
546        fn add_boolean_value(&self, name: &str, val: bool) -> &Self {
547            let cname = CString::new(name).unwrap();
548            let cval: boolean_t = if val { 1 } else { 0 };
549            let rc = unsafe { nvlist_add_boolean_value(self.ptr, cname.as_ptr(), cval) };
550            assert_eq!(rc, 0, "nvlist_add_boolean_value failed for {name}");
551            self
552        }
553
554        fn add_byte(&self, name: &str, val: u8) -> &Self {
555            let cname = CString::new(name).unwrap();
556            let rc = unsafe { nvlist_add_byte(self.ptr, cname.as_ptr(), val) };
557            assert_eq!(rc, 0, "nvlist_add_byte failed for {name}");
558            self
559        }
560
561        fn add_int8(&self, name: &str, val: i8) -> &Self {
562            let cname = CString::new(name).unwrap();
563            let rc = unsafe { nvlist_add_int8(self.ptr, cname.as_ptr(), val) };
564            assert_eq!(rc, 0, "nvlist_add_int8 failed for {name}");
565            self
566        }
567
568        fn add_uint8(&self, name: &str, val: u8) -> &Self {
569            let cname = CString::new(name).unwrap();
570            let rc = unsafe { nvlist_add_uint8(self.ptr, cname.as_ptr(), val) };
571            assert_eq!(rc, 0, "nvlist_add_uint8 failed for {name}");
572            self
573        }
574
575        fn add_int16(&self, name: &str, val: i16) -> &Self {
576            let cname = CString::new(name).unwrap();
577            let rc = unsafe { nvlist_add_int16(self.ptr, cname.as_ptr(), val) };
578            assert_eq!(rc, 0, "nvlist_add_int16 failed for {name}");
579            self
580        }
581
582        fn add_uint16(&self, name: &str, val: u16) -> &Self {
583            let cname = CString::new(name).unwrap();
584            let rc = unsafe { nvlist_add_uint16(self.ptr, cname.as_ptr(), val) };
585            assert_eq!(rc, 0, "nvlist_add_uint16 failed for {name}");
586            self
587        }
588
589        fn add_int32(&self, name: &str, val: i32) -> &Self {
590            let cname = CString::new(name).unwrap();
591            let rc = unsafe { nvlist_add_int32(self.ptr, cname.as_ptr(), val) };
592            assert_eq!(rc, 0, "nvlist_add_int32 failed for {name}");
593            self
594        }
595
596        fn add_uint32(&self, name: &str, val: u32) -> &Self {
597            let cname = CString::new(name).unwrap();
598            let rc = unsafe { nvlist_add_uint32(self.ptr, cname.as_ptr(), val) };
599            assert_eq!(rc, 0, "nvlist_add_uint32 failed for {name}");
600            self
601        }
602
603        fn add_int64(&self, name: &str, val: i64) -> &Self {
604            let cname = CString::new(name).unwrap();
605            let rc = unsafe { nvlist_add_int64(self.ptr, cname.as_ptr(), val) };
606            assert_eq!(rc, 0, "nvlist_add_int64 failed for {name}");
607            self
608        }
609
610        fn add_uint64(&self, name: &str, val: u64) -> &Self {
611            let cname = CString::new(name).unwrap();
612            let rc = unsafe { nvlist_add_uint64(self.ptr, cname.as_ptr(), val) };
613            assert_eq!(rc, 0, "nvlist_add_uint64 failed for {name}");
614            self
615        }
616
617        fn add_double(&self, name: &str, val: f64) -> &Self {
618            let cname = CString::new(name).unwrap();
619            let rc = unsafe { nvlist_add_double(self.ptr, cname.as_ptr(), val) };
620            assert_eq!(rc, 0, "nvlist_add_double failed for {name}");
621            self
622        }
623
624        fn add_string(&self, name: &str, val: &str) -> &Self {
625            let cname = CString::new(name).unwrap();
626            let cval = CString::new(val).unwrap();
627            let rc = unsafe { nvlist_add_string(self.ptr, cname.as_ptr(), cval.as_ptr()) };
628            assert_eq!(rc, 0, "nvlist_add_string failed for {name}");
629            self
630        }
631
632        fn add_hrtime(&self, name: &str, val: i64) -> &Self {
633            let cname = CString::new(name).unwrap();
634            let rc = unsafe { nvlist_add_hrtime(self.ptr, cname.as_ptr(), val) };
635            assert_eq!(rc, 0, "nvlist_add_hrtime failed for {name}");
636            self
637        }
638
639        fn add_nvlist(&self, name: &str, child: &NvListBuilder) -> &Self {
640            let cname = CString::new(name).unwrap();
641            let rc = unsafe { nvlist_add_nvlist(self.ptr, cname.as_ptr(), child.ptr) };
642            assert_eq!(rc, 0, "nvlist_add_nvlist failed for {name}");
643            self
644        }
645
646        fn add_boolean_array(&self, name: &str, vals: &[bool]) -> &Self {
647            let cname = CString::new(name).unwrap();
648            let cvals: Vec<boolean_t> = vals.iter().map(|&v| if v { 1 } else { 0 }).collect();
649            let rc = unsafe {
650                nvlist_add_boolean_array(
651                    self.ptr,
652                    cname.as_ptr(),
653                    cvals.as_ptr() as *mut boolean_t,
654                    cvals.len() as uint_t,
655                )
656            };
657            assert_eq!(rc, 0, "nvlist_add_boolean_array failed for {name}");
658            self
659        }
660
661        fn add_byte_array(&self, name: &str, vals: &[u8]) -> &Self {
662            let cname = CString::new(name).unwrap();
663            let rc = unsafe {
664                nvlist_add_byte_array(
665                    self.ptr,
666                    cname.as_ptr(),
667                    vals.as_ptr() as *mut u8,
668                    vals.len() as uint_t,
669                )
670            };
671            assert_eq!(rc, 0, "nvlist_add_byte_array failed for {name}");
672            self
673        }
674
675        fn add_int8_array(&self, name: &str, vals: &[i8]) -> &Self {
676            let cname = CString::new(name).unwrap();
677            let rc = unsafe {
678                nvlist_add_int8_array(
679                    self.ptr,
680                    cname.as_ptr(),
681                    vals.as_ptr() as *mut i8,
682                    vals.len() as uint_t,
683                )
684            };
685            assert_eq!(rc, 0, "nvlist_add_int8_array failed for {name}");
686            self
687        }
688
689        fn add_uint8_array(&self, name: &str, vals: &[u8]) -> &Self {
690            let cname = CString::new(name).unwrap();
691            let rc = unsafe {
692                nvlist_add_uint8_array(
693                    self.ptr,
694                    cname.as_ptr(),
695                    vals.as_ptr() as *mut u8,
696                    vals.len() as uint_t,
697                )
698            };
699            assert_eq!(rc, 0, "nvlist_add_uint8_array failed for {name}");
700            self
701        }
702
703        fn add_int16_array(&self, name: &str, vals: &[i16]) -> &Self {
704            let cname = CString::new(name).unwrap();
705            let rc = unsafe {
706                nvlist_add_int16_array(
707                    self.ptr,
708                    cname.as_ptr(),
709                    vals.as_ptr() as *mut i16,
710                    vals.len() as uint_t,
711                )
712            };
713            assert_eq!(rc, 0, "nvlist_add_int16_array failed for {name}");
714            self
715        }
716
717        fn add_uint16_array(&self, name: &str, vals: &[u16]) -> &Self {
718            let cname = CString::new(name).unwrap();
719            let rc = unsafe {
720                nvlist_add_uint16_array(
721                    self.ptr,
722                    cname.as_ptr(),
723                    vals.as_ptr() as *mut u16,
724                    vals.len() as uint_t,
725                )
726            };
727            assert_eq!(rc, 0, "nvlist_add_uint16_array failed for {name}");
728            self
729        }
730
731        fn add_int32_array(&self, name: &str, vals: &[i32]) -> &Self {
732            let cname = CString::new(name).unwrap();
733            let rc = unsafe {
734                nvlist_add_int32_array(
735                    self.ptr,
736                    cname.as_ptr(),
737                    vals.as_ptr() as *mut i32,
738                    vals.len() as uint_t,
739                )
740            };
741            assert_eq!(rc, 0, "nvlist_add_int32_array failed for {name}");
742            self
743        }
744
745        fn add_uint32_array(&self, name: &str, vals: &[u32]) -> &Self {
746            let cname = CString::new(name).unwrap();
747            let rc = unsafe {
748                nvlist_add_uint32_array(
749                    self.ptr,
750                    cname.as_ptr(),
751                    vals.as_ptr() as *mut u32,
752                    vals.len() as uint_t,
753                )
754            };
755            assert_eq!(rc, 0, "nvlist_add_uint32_array failed for {name}");
756            self
757        }
758
759        fn add_int64_array(&self, name: &str, vals: &[i64]) -> &Self {
760            let cname = CString::new(name).unwrap();
761            let rc = unsafe {
762                nvlist_add_int64_array(
763                    self.ptr,
764                    cname.as_ptr(),
765                    vals.as_ptr() as *mut i64,
766                    vals.len() as uint_t,
767                )
768            };
769            assert_eq!(rc, 0, "nvlist_add_int64_array failed for {name}");
770            self
771        }
772
773        fn add_uint64_array(&self, name: &str, vals: &[u64]) -> &Self {
774            let cname = CString::new(name).unwrap();
775            let rc = unsafe {
776                nvlist_add_uint64_array(
777                    self.ptr,
778                    cname.as_ptr(),
779                    vals.as_ptr() as *mut u64,
780                    vals.len() as uint_t,
781                )
782            };
783            assert_eq!(rc, 0, "nvlist_add_uint64_array failed for {name}");
784            self
785        }
786
787        fn add_string_array(&self, name: &str, vals: &[&str]) -> &Self {
788            let cname = CString::new(name).unwrap();
789            let cvals: Vec<CString> = vals.iter().map(|s| CString::new(*s).unwrap()).collect();
790            let ptrs: Vec<*mut c_char> = cvals.iter().map(|c| c.as_ptr() as *mut c_char).collect();
791            let rc = unsafe {
792                nvlist_add_string_array(
793                    self.ptr,
794                    cname.as_ptr(),
795                    ptrs.as_ptr(),
796                    ptrs.len() as uint_t,
797                )
798            };
799            assert_eq!(rc, 0, "nvlist_add_string_array failed for {name}");
800            self
801        }
802
803        fn add_nvlist_array(&self, name: &str, children: &[&NvListBuilder]) -> &Self {
804            let cname = CString::new(name).unwrap();
805            let mut ptrs: Vec<*mut nvlist_t> = children.iter().map(|c| c.ptr).collect();
806            let rc = unsafe {
807                nvlist_add_nvlist_array(
808                    self.ptr,
809                    cname.as_ptr(),
810                    ptrs.as_mut_ptr(),
811                    ptrs.len() as uint_t,
812                )
813            };
814            assert_eq!(rc, 0, "nvlist_add_nvlist_array failed for {name}");
815            self
816        }
817    }
818
819    impl Drop for NvListBuilder {
820        fn drop(&mut self) {
821            unsafe { nvlist_free(self.ptr) }
822        }
823    }
824
825    // ---- Scalar type tests ----
826
827    #[test]
828    fn test_boolean() {
829        let nvl = NvListBuilder::new();
830        nvl.add_boolean("flag");
831        let result = nvl.to_rust();
832        assert_eq!(result.lookup("flag"), Some(&NvValue::Boolean));
833    }
834
835    #[test]
836    fn test_boolean_value_true() {
837        let nvl = NvListBuilder::new();
838        nvl.add_boolean_value("enabled", true);
839        let result = nvl.to_rust();
840        assert_eq!(result.lookup("enabled"), Some(&NvValue::BooleanValue(true)));
841    }
842
843    #[test]
844    fn test_boolean_value_false() {
845        let nvl = NvListBuilder::new();
846        nvl.add_boolean_value("enabled", false);
847        let result = nvl.to_rust();
848        assert_eq!(
849            result.lookup("enabled"),
850            Some(&NvValue::BooleanValue(false))
851        );
852    }
853
854    #[test]
855    fn test_byte() {
856        let nvl = NvListBuilder::new();
857        nvl.add_byte("b", 0xAB);
858        let result = nvl.to_rust();
859        assert_eq!(result.lookup("b"), Some(&NvValue::Byte(0xAB)));
860    }
861
862    #[test]
863    fn test_int8() {
864        let nvl = NvListBuilder::new();
865        nvl.add_int8("v", i8::MIN);
866        let result = nvl.to_rust();
867        assert_eq!(result.lookup("v"), Some(&NvValue::Int8(i8::MIN)));
868    }
869
870    #[test]
871    fn test_uint8() {
872        let nvl = NvListBuilder::new();
873        nvl.add_uint8("v", u8::MAX);
874        let result = nvl.to_rust();
875        assert_eq!(result.lookup("v"), Some(&NvValue::UInt8(u8::MAX)));
876    }
877
878    #[test]
879    fn test_int16() {
880        let nvl = NvListBuilder::new();
881        nvl.add_int16("v", i16::MIN);
882        let result = nvl.to_rust();
883        assert_eq!(result.lookup("v"), Some(&NvValue::Int16(i16::MIN)));
884    }
885
886    #[test]
887    fn test_uint16() {
888        let nvl = NvListBuilder::new();
889        nvl.add_uint16("v", u16::MAX);
890        let result = nvl.to_rust();
891        assert_eq!(result.lookup("v"), Some(&NvValue::UInt16(u16::MAX)));
892    }
893
894    #[test]
895    fn test_int32() {
896        let nvl = NvListBuilder::new();
897        nvl.add_int32("v", i32::MIN);
898        let result = nvl.to_rust();
899        assert_eq!(result.lookup("v"), Some(&NvValue::Int32(i32::MIN)));
900    }
901
902    #[test]
903    fn test_uint32() {
904        let nvl = NvListBuilder::new();
905        nvl.add_uint32("v", u32::MAX);
906        let result = nvl.to_rust();
907        assert_eq!(result.lookup("v"), Some(&NvValue::UInt32(u32::MAX)));
908    }
909
910    #[test]
911    fn test_int64() {
912        let nvl = NvListBuilder::new();
913        nvl.add_int64("v", i64::MIN);
914        let result = nvl.to_rust();
915        assert_eq!(result.lookup("v"), Some(&NvValue::Int64(i64::MIN)));
916    }
917
918    #[test]
919    fn test_uint64() {
920        let nvl = NvListBuilder::new();
921        nvl.add_uint64("v", u64::MAX);
922        let result = nvl.to_rust();
923        assert_eq!(result.lookup("v"), Some(&NvValue::UInt64(u64::MAX)));
924    }
925
926    #[test]
927    fn test_double() {
928        let nvl = NvListBuilder::new();
929        nvl.add_double("pi", std::f64::consts::PI);
930        let result = nvl.to_rust();
931        assert_eq!(
932            result.lookup("pi"),
933            Some(&NvValue::Double(std::f64::consts::PI))
934        );
935    }
936
937    #[test]
938    fn test_string() {
939        let nvl = NvListBuilder::new();
940        nvl.add_string("greeting", "hello world");
941        let result = nvl.to_rust();
942        assert_eq!(
943            result.lookup("greeting"),
944            Some(&NvValue::String("hello world".into()))
945        );
946    }
947
948    #[test]
949    fn test_hrtime() {
950        let nvl = NvListBuilder::new();
951        nvl.add_hrtime("ts", 123_456_789);
952        let result = nvl.to_rust();
953        assert_eq!(result.lookup("ts"), Some(&NvValue::Hrtime(123_456_789)));
954    }
955
956    // ---- Array type tests ----
957
958    #[test]
959    fn test_boolean_array() {
960        let nvl = NvListBuilder::new();
961        nvl.add_boolean_array("flags", &[true, false, true]);
962        let result = nvl.to_rust();
963        assert_eq!(
964            result.lookup("flags"),
965            Some(&NvValue::BooleanArray(vec![true, false, true]))
966        );
967    }
968
969    #[test]
970    fn test_byte_array() {
971        let nvl = NvListBuilder::new();
972        nvl.add_byte_array("data", &[1, 2, 3]);
973        let result = nvl.to_rust();
974        assert_eq!(
975            result.lookup("data"),
976            Some(&NvValue::ByteArray(vec![1, 2, 3]))
977        );
978    }
979
980    #[test]
981    fn test_int8_array() {
982        let nvl = NvListBuilder::new();
983        nvl.add_int8_array("vals", &[i8::MIN, 0, i8::MAX]);
984        let result = nvl.to_rust();
985        assert_eq!(
986            result.lookup("vals"),
987            Some(&NvValue::Int8Array(vec![i8::MIN, 0, i8::MAX]))
988        );
989    }
990
991    #[test]
992    fn test_uint8_array() {
993        let nvl = NvListBuilder::new();
994        nvl.add_uint8_array("vals", &[0, 128, u8::MAX]);
995        let result = nvl.to_rust();
996        assert_eq!(
997            result.lookup("vals"),
998            Some(&NvValue::UInt8Array(vec![0, 128, u8::MAX]))
999        );
1000    }
1001
1002    #[test]
1003    fn test_int16_array() {
1004        let nvl = NvListBuilder::new();
1005        nvl.add_int16_array("vals", &[i16::MIN, 0, i16::MAX]);
1006        let result = nvl.to_rust();
1007        assert_eq!(
1008            result.lookup("vals"),
1009            Some(&NvValue::Int16Array(vec![i16::MIN, 0, i16::MAX]))
1010        );
1011    }
1012
1013    #[test]
1014    fn test_uint16_array() {
1015        let nvl = NvListBuilder::new();
1016        nvl.add_uint16_array("vals", &[0, u16::MAX]);
1017        let result = nvl.to_rust();
1018        assert_eq!(
1019            result.lookup("vals"),
1020            Some(&NvValue::UInt16Array(vec![0, u16::MAX]))
1021        );
1022    }
1023
1024    #[test]
1025    fn test_int32_array() {
1026        let nvl = NvListBuilder::new();
1027        nvl.add_int32_array("vals", &[i32::MIN, 0, i32::MAX]);
1028        let result = nvl.to_rust();
1029        assert_eq!(
1030            result.lookup("vals"),
1031            Some(&NvValue::Int32Array(vec![i32::MIN, 0, i32::MAX]))
1032        );
1033    }
1034
1035    #[test]
1036    fn test_uint32_array() {
1037        let nvl = NvListBuilder::new();
1038        nvl.add_uint32_array("vals", &[0, u32::MAX]);
1039        let result = nvl.to_rust();
1040        assert_eq!(
1041            result.lookup("vals"),
1042            Some(&NvValue::UInt32Array(vec![0, u32::MAX]))
1043        );
1044    }
1045
1046    #[test]
1047    fn test_int64_array() {
1048        let nvl = NvListBuilder::new();
1049        nvl.add_int64_array("vals", &[i64::MIN, 0, i64::MAX]);
1050        let result = nvl.to_rust();
1051        assert_eq!(
1052            result.lookup("vals"),
1053            Some(&NvValue::Int64Array(vec![i64::MIN, 0, i64::MAX]))
1054        );
1055    }
1056
1057    #[test]
1058    fn test_uint64_array() {
1059        let nvl = NvListBuilder::new();
1060        nvl.add_uint64_array("vals", &[0, u64::MAX]);
1061        let result = nvl.to_rust();
1062        assert_eq!(
1063            result.lookup("vals"),
1064            Some(&NvValue::UInt64Array(vec![0, u64::MAX]))
1065        );
1066    }
1067
1068    #[test]
1069    fn test_string_array() {
1070        let nvl = NvListBuilder::new();
1071        nvl.add_string_array("names", &["alpha", "beta", "gamma"]);
1072        let result = nvl.to_rust();
1073        assert_eq!(
1074            result.lookup("names"),
1075            Some(&NvValue::StringArray(vec![
1076                "alpha".into(),
1077                "beta".into(),
1078                "gamma".into(),
1079            ]))
1080        );
1081    }
1082
1083    #[test]
1084    fn test_nvlist_array() {
1085        let child1 = NvListBuilder::new();
1086        child1.add_string("name", "first");
1087        let child2 = NvListBuilder::new();
1088        child2.add_string("name", "second");
1089
1090        let nvl = NvListBuilder::new();
1091        nvl.add_nvlist_array("items", &[&child1, &child2]);
1092
1093        let result = nvl.to_rust();
1094        let items = result.lookup("items").unwrap();
1095        if let NvValue::NvListArray(arr) = items {
1096            assert_eq!(arr.len(), 2);
1097            assert_eq!(
1098                arr[0].lookup("name"),
1099                Some(&NvValue::String("first".into()))
1100            );
1101            assert_eq!(
1102                arr[1].lookup("name"),
1103                Some(&NvValue::String("second".into()))
1104            );
1105        } else {
1106            panic!("expected NvListArray, got {items:?}");
1107        }
1108    }
1109
1110    // ---- Structural / edge case tests ----
1111
1112    #[test]
1113    fn test_empty_nvlist() {
1114        let nvl = NvListBuilder::new();
1115        let result = nvl.to_rust();
1116        assert!(result.is_empty());
1117        assert_eq!(result.len(), 0);
1118    }
1119
1120    #[test]
1121    fn test_multiple_pairs_in_order() {
1122        let nvl = NvListBuilder::new();
1123        nvl.add_string("first", "a");
1124        nvl.add_int32("second", 42);
1125        nvl.add_boolean("third");
1126        nvl.add_uint64("fourth", 999);
1127        nvl.add_double("fifth", 2.5);
1128
1129        let result = nvl.to_rust();
1130        assert_eq!(result.len(), 5);
1131
1132        let pairs: Vec<_> = result.iter().collect();
1133        assert_eq!(pairs[0].0, "first");
1134        assert_eq!(pairs[1].0, "second");
1135        assert_eq!(pairs[2].0, "third");
1136        assert_eq!(pairs[3].0, "fourth");
1137        assert_eq!(pairs[4].0, "fifth");
1138
1139        assert_eq!(*pairs[0].1, NvValue::String("a".into()));
1140        assert_eq!(*pairs[1].1, NvValue::Int32(42));
1141        assert_eq!(*pairs[2].1, NvValue::Boolean);
1142        assert_eq!(*pairs[3].1, NvValue::UInt64(999));
1143        assert_eq!(*pairs[4].1, NvValue::Double(2.5));
1144    }
1145
1146    #[test]
1147    fn test_nested_nvlist() {
1148        let inner = NvListBuilder::new();
1149        inner.add_string("key", "value");
1150
1151        let outer = NvListBuilder::new();
1152        outer.add_nvlist("child", &inner);
1153
1154        let result = outer.to_rust();
1155        if let Some(NvValue::NvList(child)) = result.lookup("child") {
1156            assert_eq!(child.lookup("key"), Some(&NvValue::String("value".into())));
1157        } else {
1158            panic!("expected nested NvList");
1159        }
1160    }
1161
1162    #[test]
1163    fn test_deeply_nested_nvlist() {
1164        let innermost = NvListBuilder::new();
1165        innermost.add_string("depth", "three");
1166
1167        let middle = NvListBuilder::new();
1168        middle.add_nvlist("inner", &innermost);
1169
1170        let outer = NvListBuilder::new();
1171        outer.add_nvlist("middle", &middle);
1172
1173        let result = outer.to_rust();
1174        if let Some(NvValue::NvList(mid)) = result.lookup("middle") {
1175            if let Some(NvValue::NvList(inn)) = mid.lookup("inner") {
1176                assert_eq!(inn.lookup("depth"), Some(&NvValue::String("three".into())));
1177            } else {
1178                panic!("expected inner NvList");
1179            }
1180        } else {
1181            panic!("expected middle NvList");
1182        }
1183    }
1184
1185    #[test]
1186    fn test_empty_string() {
1187        let nvl = NvListBuilder::new();
1188        nvl.add_string("empty", "");
1189        let result = nvl.to_rust();
1190        assert_eq!(
1191            result.lookup("empty"),
1192            Some(&NvValue::String(String::new()))
1193        );
1194    }
1195
1196    #[test]
1197    fn test_lookup_missing_key() {
1198        let nvl = NvListBuilder::new();
1199        nvl.add_string("exists", "yes");
1200        let result = nvl.to_rust();
1201        assert_eq!(
1202            result.lookup("exists"),
1203            Some(&NvValue::String("yes".into()))
1204        );
1205        assert_eq!(result.lookup("missing"), None);
1206    }
1207}