use libc::ENOMEM;
use libnv_sys::*;
use std::{convert::{From, Into},
ffi::CStr,
os::{raw::{c_char, c_void},
unix::io::AsRawFd},
slice};
use crate::{IntoCStr, NvError, NvResult};
pub enum NvType {
None = 0,
Null = 1,
Bool = 2,
Number = 3,
String = 4,
NvList = 5,
Descriptor = 6,
Binary = 7,
BoolArray = 8,
NumberArray = 9,
StringArray = 10,
NvListArray = 11,
DescriptorArray = 12,
}
#[repr(i32)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum NvFlag {
None = 0,
IgnoreCase = 1,
NoUnique = 2,
Both = 3,
}
impl From<i32> for NvFlag {
fn from(source: i32) -> Self {
match source {
0 => NvFlag::None,
1 => NvFlag::IgnoreCase,
2 => NvFlag::NoUnique,
3 => NvFlag::Both,
_ => panic!("Incorrect value passed to NvFlag"),
}
}
}
macro_rules! impl_list_op {
($type_:ty, $method:ident, false) => {
impl NvTypeOp for $type_ {
fn add_to_list<'a, N: IntoCStr<'a>>(&self, list: &mut NvList, name: N) -> NvResult<()> {
return list.$method(name, *self);
}
}
};
($type_:ty, $method:ident, true) => {
impl NvTypeOp for $type_ {
fn add_to_list<'a, N: IntoCStr<'a>>(&self, list: &mut NvList, name: N) -> NvResult<()> {
return list.$method(name, &*self);
}
}
};
}
pub trait NvTypeOp {
fn add_to_list<'a, N: IntoCStr<'a>>(&self, list: &mut NvList, name: N) -> NvResult<()>;
}
impl_list_op! {bool, insert_bool, false}
impl_list_op! {[bool], insert_bools, true}
impl_list_op! {u8, insert_number, false}
impl_list_op! {u16, insert_number, false}
impl_list_op! {u32, insert_number, false}
impl_list_op! {u64, insert_number, false}
impl_list_op! {[u64], insert_numbers, true}
impl_list_op! {str, insert_string, true}
impl_list_op! {NvList, insert_nvlist, true}
impl<T> NvTypeOp for Option<T>
where
T: NvTypeOp,
{
fn add_to_list<'a, N: IntoCStr<'a>>(&self, list: &mut NvList, name: N) -> NvResult<()> {
match &self {
Some(val) => val.add_to_list(list, name),
None => list.insert_null(name),
}
}
}
#[derive(Debug)]
pub struct NvList {
ptr: *mut nvlist_t,
}
#[derive(Debug)]
pub struct PackedNvList {
ptr: *mut c_void,
size: usize,
}
#[derive(Debug)]
pub struct BorrowedPackedNvList<'a> {
buf: &'a [u8],
}
impl<'a> BorrowedPackedNvList<'a> {
pub fn from_raw(buf: &'a [u8]) -> Self { BorrowedPackedNvList { buf } }
pub fn as_ptr(&self) -> *const c_void { self.buf.as_ptr() as *const c_void }
pub fn as_mut_ptr(&mut self) -> *mut c_void { self.buf.as_ptr() as *mut c_void }
#[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.buf.len() }
pub fn unpack(&self, flags: NvFlag) -> NvResult<NvList> {
let raw =
unsafe { nvlist_unpack(self.buf.as_ptr() as *const c_void, self.len(), flags as i32) };
if raw.is_null() {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
Err(NvError::from_errno(errno))
} else {
Ok(NvList { ptr: raw })
}
}
}
impl PackedNvList {
pub fn as_ptr(&self) -> *const c_void { self.ptr }
pub fn as_mut_ptr(&mut self) -> *mut c_void { self.ptr }
#[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.size }
pub fn unpack(&self, flags: NvFlag) -> NvResult<NvList> {
let raw = unsafe { nvlist_unpack(self.ptr, self.size, flags as i32) };
if raw.is_null() {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
Err(NvError::from_errno(errno))
} else {
Ok(NvList { ptr: raw })
}
}
}
impl Drop for PackedNvList {
fn drop(&mut self) {
unsafe {
libc::free(self.ptr);
}
}
}
#[doc(hidden)]
impl Default for NvList {
fn default() -> NvList { NvList::new(NvFlag::None).expect("Failed to create new list") }
}
impl NvList {
pub fn as_ptr(&self) -> *mut nvlist_t { self.ptr }
fn check_if_error(&self) -> NvResult<()> {
match self.error() {
0 => Ok(()),
errno => Err(NvError::NativeError(errno)),
}
}
pub fn new(flags: NvFlag) -> NvResult<NvList> {
let raw_list = unsafe { nvlist_create(flags as i32) };
if raw_list.is_null() {
Err(NvError::NativeError(ENOMEM))
} else {
Ok(NvList { ptr: raw_list })
}
}
pub unsafe fn from_ptr(ptr: *mut nvlist_t) -> Self { Self { ptr } }
pub fn is_empty(&self) -> bool { unsafe { nvlist_empty(self.ptr) } }
pub fn flags(&self) -> NvFlag { NvFlag::from(unsafe { nvlist_flags(self.ptr) }) }
pub fn error(&self) -> i32 { unsafe { nvlist_error(self.ptr) } }
pub fn set_error(&mut self, error: i32) -> NvResult<()> {
if self.error() != 0 {
Err(NvError::AlreadySet)
} else {
unsafe { nvlist_set_error(self.ptr, error) }
Ok(())
}
}
pub fn insert<'a, N: IntoCStr<'a>, T: NvTypeOp>(&mut self, name: N, value: T) -> NvResult<()> {
value.add_to_list(self, name)
}
pub fn insert_null<'a, N: IntoCStr<'a>>(&mut self, name: N) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_add_null(self.ptr, c_name.as_ptr());
}
self.check_if_error()
}
pub fn insert_number<'a, N: IntoCStr<'a>, I: Into<u64>>(
&mut self,
name: N,
value: I,
) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_add_number(self.ptr, c_name.as_ptr(), value.into());
}
self.check_if_error()
}
pub fn insert_bool<'a, N: IntoCStr<'a>>(&mut self, name: N, value: bool) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_add_bool(self.ptr, c_name.as_ptr(), value);
}
self.check_if_error()
}
pub fn insert_string<'a, 'b, N: IntoCStr<'a>, V: IntoCStr<'b>>(
&mut self,
name: N,
value: V,
) -> NvResult<()> {
let c_name = name.into_c_str()?;
let c_value = value.into_c_str()?;
unsafe {
nvlist_add_string(self.ptr, c_name.as_ptr(), c_value.as_ptr());
}
self.check_if_error()
}
pub fn insert_nvlist<'a, N: IntoCStr<'a>>(&mut self, name: N, value: &NvList) -> NvResult<()> {
let c_name = name.into_c_str()?;
if !value.as_ptr().is_null() {
unsafe {
nvlist_add_nvlist(self.ptr, c_name.as_ptr(), value.as_ptr());
}
}
self.check_if_error()
}
#[deprecated(since = "0.4.0", note = "use insert_binary instead")]
pub unsafe fn add_binary<'a, N: IntoCStr<'a>>(
&mut self,
name: N,
value: *const i8,
size: usize,
) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_add_binary(self.ptr, c_name.as_ptr(), value as *const c_void, size);
}
self.check_if_error()
}
pub fn insert_binary<'a, N: IntoCStr<'a>>(&mut self, name: N, value: &[u8]) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_add_binary(
self.ptr,
c_name.as_ptr(),
value.as_ptr() as *const c_void,
value.len(),
);
}
self.check_if_error()
}
pub fn insert_bools<'a, N: IntoCStr<'a>>(&mut self, name: N, value: &[bool]) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_add_bool_array(self.ptr, c_name.as_ptr(), value.as_ptr(), value.len());
}
self.check_if_error()
}
pub fn insert_numbers<'a, N: IntoCStr<'a>>(&mut self, name: N, value: &[u64]) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_add_number_array(self.ptr, c_name.as_ptr(), value.as_ptr(), value.len());
}
self.check_if_error()
}
pub fn insert_strings<'a, 'b, N: IntoCStr<'a>, V: IntoCStr<'b>, I: IntoIterator<Item = V>>(
&mut self,
name: N,
value: I,
) -> NvResult<()> {
let c_name = name.into_c_str()?;
let strings = value.into_iter().map(IntoCStr::into_c_str).collect::<NvResult<Vec<_>>>()?;
unsafe {
let pointers: Vec<*const c_char> = strings.iter().map(|e| e.as_ptr()).collect();
nvlist_add_string_array(
self.ptr,
c_name.as_ptr(),
pointers.as_slice().as_ptr(),
strings.len(),
);
}
self.check_if_error()
}
pub fn insert_nvlists<'a, N: IntoCStr<'a>>(
&mut self,
name: N,
value: &[NvList],
) -> NvResult<()> {
let c_name = name.into_c_str()?;
let vec = value.to_vec();
unsafe {
let lists: Vec<*const nvlist_t> =
vec.iter().map(|item| item.as_ptr() as *const nvlist_t).collect();
nvlist_add_nvlist_array(
self.ptr,
c_name.as_ptr(),
lists.as_slice().as_ptr(),
lists.len(),
);
}
self.check_if_error()
}
pub fn contains_key<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<bool> {
let c_name = name.into_c_str()?;
unsafe { Ok(nvlist_exists(self.ptr, c_name.as_ptr())) }
}
pub fn contains_key_with_type<'a, N: IntoCStr<'a>>(
&self,
name: N,
ty: NvType,
) -> NvResult<bool> {
let c_name = name.into_c_str()?;
unsafe { Ok(nvlist_exists_type(self.ptr, c_name.as_ptr(), ty as i32)) }
}
pub fn get_binary<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<&[u8]>> {
let c_name = name.into_c_str()?;
unsafe {
let mut size: usize = 0;
let ret = nvlist_get_binary(self.ptr, c_name.as_ptr(), &mut size as *mut usize);
if ret.is_null() {
Ok(None)
} else {
Ok(Some(slice::from_raw_parts(ret as *const u8, size)))
}
}
}
pub fn get_bool<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<bool>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_bool(self.ptr, c_name.as_ptr()) {
Ok(Some(nvlist_get_bool(self.ptr, c_name.as_ptr())))
} else {
Ok(None)
}
}
}
pub fn get_number<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<u64>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_number(self.ptr, c_name.as_ptr()) {
Ok(Some(nvlist_get_number(self.ptr, c_name.as_ptr())))
} else {
Ok(None)
}
}
}
pub fn get_string<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<String>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_string(self.ptr, c_name.as_ptr()) {
let ret = nvlist_get_string(self.ptr, c_name.as_ptr());
if ret.is_null() {
Ok(None)
} else {
Ok(Some(CStr::from_ptr(ret).to_string_lossy().into_owned()))
}
} else {
Ok(None)
}
}
}
pub fn get_nvlist<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<NvList>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_nvlist(self.ptr, c_name.as_ptr()) {
let res = nvlist_get_nvlist(self.ptr, c_name.as_ptr());
Ok(Some(NvList { ptr: nvlist_clone(res) }))
} else {
Ok(None)
}
}
}
pub fn get_bools<'a, 'b, N: IntoCStr<'b>>(&'a self, name: N) -> NvResult<Option<&'a [bool]>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_bool_array(self.ptr, c_name.as_ptr()) {
let mut len: usize = 0;
let arr = nvlist_get_bool_array(self.ptr, c_name.as_ptr(), &mut len as *mut usize);
Ok(Some(slice::from_raw_parts(arr, len)))
} else {
Ok(None)
}
}
}
pub fn get_numbers<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<&[u64]>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_number_array(self.ptr, c_name.as_ptr()) {
let mut len: usize = 0;
let arr =
nvlist_get_number_array(self.ptr, c_name.as_ptr(), &mut len as *mut usize);
Ok(Some(slice::from_raw_parts(arr, len)))
} else {
Ok(None)
}
}
}
pub fn get_strings<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<Vec<String>>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_string_array(self.ptr, c_name.as_ptr()) {
let mut len: usize = 0;
let arr =
nvlist_get_string_array(self.ptr, c_name.as_ptr(), &mut len as *mut usize);
let slice = slice::from_raw_parts(arr, len);
let strings = slice
.iter()
.copied()
.map(|ptr| String::from(CStr::from_ptr(ptr).to_string_lossy()))
.collect();
Ok(Some(strings))
} else {
Ok(None)
}
}
}
pub fn get_nvlists<'a, N: IntoCStr<'a>>(&self, name: N) -> NvResult<Option<Vec<NvList>>> {
let c_name = name.into_c_str()?;
unsafe {
if nvlist_exists_nvlist_array(self.ptr, c_name.as_ptr()) {
let mut len: usize = 0;
let arr =
nvlist_get_nvlist_array(self.ptr, c_name.as_ptr(), &mut len as *mut usize);
let slice = slice::from_raw_parts(arr, len);
Ok(Some(slice.iter().map(|item| NvList { ptr: nvlist_clone(*item) }).collect()))
} else {
Ok(None)
}
}
}
pub fn dump<T: AsRawFd>(&self, out: T) -> NvResult<()> {
unsafe { nvlist_dump(self.ptr, out.as_raw_fd()) }
self.check_if_error()
}
pub fn len(&self) -> usize { unsafe { nvlist_size(self.ptr) } }
pub fn remove<'a, N: IntoCStr<'a>>(&mut self, name: N) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_free(self.ptr, c_name.as_ptr());
}
self.check_if_error()
}
pub fn remove_with_type<'a, N: IntoCStr<'a>>(&mut self, name: N, ty: NvType) -> NvResult<()> {
let c_name = name.into_c_str()?;
unsafe {
nvlist_free_type(self.ptr, c_name.as_ptr(), ty as i32);
}
self.check_if_error()
}
pub fn pack(&self) -> NvResult<PackedNvList> {
let mut packed = PackedNvList { ptr: std::ptr::null_mut(), size: 0 };
let ptr = unsafe { nvlist_pack(self.ptr, &mut packed.size) };
if ptr.is_null() {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
Err(NvError::from_errno(errno))
} else {
packed.ptr = ptr;
Ok(packed)
}
}
}
impl Clone for NvList {
fn clone(&self) -> NvList { NvList { ptr: unsafe { nvlist_clone(self.ptr) } } }
}
impl Drop for NvList {
fn drop(&mut self) {
unsafe {
nvlist_destroy(self.ptr);
}
}
}
impl From<NvList> for *mut nvlist_t {
fn from(outer: NvList) -> *mut nvlist_t {
let r = outer.ptr;
std::mem::forget(outer);
r
}
}
#[cfg(test)]
mod test {
use super::*;
mod nvlist_pack {
use super::*;
#[test]
fn ok() {
let nv = NvList::new(NvFlag::None).unwrap();
let _packed = nv.pack().unwrap();
}
#[test]
fn file_descriptors() {
let nv = NvList::new(NvFlag::None).unwrap();
let name = c"foo";
unsafe {
nvlist_add_descriptor(nv.as_ptr(), name.as_ptr(), 1);
}
assert!(matches!(nv.pack().unwrap_err(), NvError::OperationNotSupported));
}
}
mod nvlist_unpack {
use super::*;
#[test]
fn bad_flags() {
let mut nv = NvList::new(NvFlag::None).unwrap();
nv.insert_number("Answer", 42u64).unwrap();
let packed = nv.pack().unwrap();
assert!(matches!(packed.unpack(NvFlag::IgnoreCase).unwrap_err(), NvError::Io(_)));
}
#[test]
fn borrowed() {
let buf = {
let mut nv = NvList::new(NvFlag::None).unwrap();
nv.insert_number("Answer", 42u64).unwrap();
let packed = nv.pack().unwrap();
let buf =
unsafe { std::slice::from_raw_parts(packed.ptr as *const u8, packed.len()) };
buf.to_vec()
};
let borrowed = BorrowedPackedNvList::from_raw(&buf);
let nv2 = borrowed.unpack(NvFlag::None).unwrap();
assert_eq!(nv2.get_number("Answer").unwrap(), Some(42u64));
}
#[test]
fn corruption() {
let mut buf = [42u8; 100];
let packed = std::mem::ManuallyDrop::new(PackedNvList {
ptr: buf.as_mut_ptr() as *mut c_void,
size: 100,
});
assert!(matches!(packed.unpack(NvFlag::None).unwrap_err(), NvError::Io(_)));
}
#[test]
fn ok() {
let mut nv = NvList::new(NvFlag::None).unwrap();
nv.insert_number("Answer", 42u64).unwrap();
let packed = nv.pack().unwrap();
let nv2 = packed.unpack(NvFlag::None).unwrap();
assert_eq!(nv2.get_number("Answer").unwrap(), Some(42u64));
}
}
}