diff --git a/proxmox-tools/src/lib.rs b/proxmox-tools/src/lib.rs index 5c1633c8..2c475d19 100644 --- a/proxmox-tools/src/lib.rs +++ b/proxmox-tools/src/lib.rs @@ -9,8 +9,12 @@ pub mod fd; pub mod fs; pub mod io; pub mod serde; +pub mod uuid; pub mod vec; +#[doc(inline)] +pub use uuid::Uuid; + /// Evaluates to the offset (in bytes) of a given member within a struct #[macro_export] macro_rules! offsetof { diff --git a/proxmox-tools/src/uuid.rs b/proxmox-tools/src/uuid.rs new file mode 100644 index 00000000..176ab291 --- /dev/null +++ b/proxmox-tools/src/uuid.rs @@ -0,0 +1,160 @@ +//! Simple bindings to libuuid's `uuid_generate`. + +use std::borrow::{Borrow, BorrowMut}; +use std::fmt; +use std::os::raw::c_int; + +use failure::{bail, Error}; + +#[link(name = "uuid")] +extern "C" { + fn uuid_generate(out: *mut [u8; 16]); + fn uuid_unparse_lower(input: *const [u8; 16], out: *mut u8); + fn uuid_unparse_upper(input: *const [u8; 16], out: *mut u8); + fn uuid_parse(input: *const u8, out: *mut [u8; 16]) -> c_int; +} + +/// Uuid generated with the system's native libuuid. +/// +/// ``` +/// use proxmox_tools::uuid::Uuid; +/// +/// let uuid = Uuid::generate(); +/// println!("Generated uuid: {}", uuid); +/// // prints somethign like: +/// // Generated uuid: d79191c-724-415f-a1bf-2256b8ea76e +/// +/// let bytes: &[u8] = uuid.as_ref(); +/// println!("raw byte string: {:?}", bytes); +/// // raw byte string: [215, 9, 25, 28, 114, 4, 65, 95, 161, 191, 34, 86, 184, 234, 118, 14] +/// +/// let text = format!("{}", uuid); +/// let parsed: Uuid = text.parse().unwrap(); +/// assert_eq!(uuid, parsed); +/// ``` +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Uuid(Box<[u8; 16]>); + +impl Uuid { + /// Generate a uuid with `uuid_generate(3)`. + pub fn generate() -> Self { + use std::alloc::{alloc, Layout}; + let uuid = unsafe { alloc(Layout::new::<[u8; 16]>()) as *mut [u8; 16] }; + unsafe { uuid_generate(uuid) }; + Self(unsafe { Box::from_raw(uuid) }) + } + + /// Get a reference to the internal 16 byte array. + pub fn as_bytes(&self) -> &[u8; 16] { + &*self.0 + } + + /// Take out the inner boxed 16 byte array. + pub fn into_inner(self) -> Box<[u8; 16]> { + self.0 + } + + /// Parse a uuid in optionally-hyphenated format. + /// + /// ``` + /// use proxmox_tools::uuid::Uuid; + /// + /// let gen = Uuid::generate(); + /// let text = format!("{}", gen); + /// let parsed: Uuid = text.parse().unwrap(); + /// assert_eq!(gen, parsed); + /// ``` + pub fn parse_str(src: &str) -> Result { + use std::alloc::{alloc, Layout}; + let uuid = unsafe { alloc(Layout::new::<[u8; 16]>()) as *mut [u8; 16] }; + let rc = unsafe { uuid_parse(src.as_bytes().as_ptr(), uuid) }; + if rc != 0 { + bail!("failed to parse uuid"); + } + Ok(Self(unsafe { Box::from_raw(uuid) })) + } +} + +impl AsRef<[u8]> for Uuid { + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl AsMut<[u8]> for Uuid { + #[inline] + fn as_mut(&mut self) -> &mut [u8] { + &mut (self.0)[..] + } +} + +impl Borrow<[u8]> for Uuid { + #[inline] + fn borrow(&self) -> &[u8] { + &(self.0)[..] + } +} + +impl BorrowMut<[u8]> for Uuid { + #[inline] + fn borrow_mut(&mut self) -> &mut [u8] { + &mut (self.0)[..] + } +} + +impl From<[u8; 16]> for Uuid { + fn from(data: [u8; 16]) -> Self { + Self(Box::new(data)) + } +} + +impl From> for Uuid { + fn from(data: Box<[u8; 16]>) -> Self { + Self(data) + } +} + +impl Into<[u8; 16]> for Uuid { + fn into(self) -> [u8; 16] { + *self.0 + } +} + +impl fmt::Display for Uuid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(self, f) + } +} + +impl fmt::LowerHex for Uuid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut buf = [0u8; 37]; + unsafe { + uuid_unparse_lower(self.as_bytes(), buf.as_mut_ptr()); + } + write!(f, "{}", unsafe { + std::str::from_utf8_unchecked(&buf[..36]) + }) + } +} + +impl fmt::UpperHex for Uuid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut buf = [0u8; 37]; + unsafe { + uuid_unparse_upper(self.as_bytes(), buf.as_mut_ptr()); + } + write!(f, "{}", unsafe { + std::str::from_utf8_unchecked(&buf[..36]) + }) + } +} + +impl std::str::FromStr for Uuid { + type Err = Error; + + fn from_str(src: &str) -> Result { + Self::parse_str(src) + } +}