1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
//! Exporting topologies to XML
//!
//! XML export can, in principle, handle every single topology that hwloc can
//! probe, but does so at the cost of more complexity than synthetic topologies.
#[cfg(doc)]
use crate::{errors::NulError, topology::builder::TopologyBuilder};
use crate::{
errors::{self, HybridError, RawHwlocError},
ffi::int,
path::{self, PathError},
topology::Topology,
};
use bitflags::bitflags;
use hwlocality_sys::{hwloc_topology_export_xml_flags_e, HWLOC_TOPOLOGY_EXPORT_XML_FLAG_V1};
#[allow(unused)]
#[cfg(test)]
use similar_asserts::assert_eq;
use std::{
borrow::Borrow,
ffi::{c_char, c_uint, CStr, OsStr},
fmt::{self, Debug, Display},
hash::Hash,
ops::{Deref, Index},
path::Path,
ptr::{self, NonNull},
};
/// # Exporting Topologies to XML
//
// --- Implementation details ---
//
// Upstream docs: https://hwloc.readthedocs.io/en/v2.9/group__hwlocality__xmlexport.html
impl Topology {
/// Export the topology into an XML file at filesystem location `path`
///
/// If no path is given, the XML output is sent to standard output.
///
/// This file may be loaded later using [`TopologyBuilder::from_xml_file()`].
///
/// By default, the latest export format is used, which means older hwloc
/// releases (e.g. v1.x) will not be able to import it. Exporting to v1.x
/// specific XML format is possible using flag
/// [`XMLExportFlags::V1`] but it may miss some details about the topology.
/// Also, note that this option will be removed from the (upcoming at the
/// time of writing) hwloc v3.0 release.
///
/// If there is any chance that the exported file may ever be imported back
/// by a process using hwloc 1.x, one should consider detecting it at
/// runtime and using the corresponding export format.
///
/// Only printable characters may be exported to XML string attributes. Any
/// other character, especially any non-ASCII character, will be silently
/// dropped.
///
/// # Errors
///
/// - [`ContainsNul`] if `path` contains NUL chars.
/// - [`NotUnicode`] if `path` contains non-Unicode data
///
/// [`ContainsNul`]: PathError::ContainsNul
/// [`NotUnicode`]: PathError::NotUnicode
#[doc(alias = "hwloc_topology_export_xml")]
pub fn export_xml_file(
&self,
path: Option<&Path>,
flags: XMLExportFlags,
) -> Result<(), HybridError<PathError>> {
let path = path::make_hwloc_path(path.unwrap_or_else(|| Path::new("-")))?;
// SAFETY: - Topology is trusted to contain a valid ptr (type invariant)
// - hwloc ops are trusted not to modify *const parameters
// - path has been checked to be fit for hwloc consumption
// - flags only allows values supported by the active hwloc version
errors::call_hwloc_int_normal("hwloc_topology_export_xml", || unsafe {
hwlocality_sys::hwloc_topology_export_xml(self.as_ptr(), path.borrow(), flags.bits())
})
.map_err(HybridError::Hwloc)?;
Ok(())
}
/// Export the topology into an XML memory buffer
///
/// This memory buffer may be loaded later using
/// [`TopologyBuilder::from_xml()`].
///
/// By default, the latest export format is used, which means older hwloc
/// releases (e.g. v1.x) will not be able to import it. Exporting to v1.x
/// specific XML format is possible using flag
/// [`XMLExportFlags::V1`] but it may miss some details about the topology.
/// Also, note that this option will be removed from the (upcoming at the
/// time of writing) hwloc v3.0 release.
///
/// If there is any chance that the exported file may ever be imported back
/// by a process using hwloc 1.x, one should consider detecting it at
/// runtime and using the corresponding export format.
///
/// Only printable characters may be exported to XML string attributes. Any
/// other character, especially any non-ASCII character, will be silently
/// dropped.
#[allow(clippy::missing_errors_doc)]
#[doc(alias = "hwloc_topology_export_xmlbuffer")]
pub fn export_xml(&self, flags: XMLExportFlags) -> Result<XML<'_>, RawHwlocError> {
let mut xmlbuffer = ptr::null_mut();
let mut buflen = 0;
// SAFETY: - Topology is trusted to contain a valid ptr (type invariant)
// - hwloc ops are trusted not to modify *const parameters
// - xmlbuffer and buflen are out parameters, their initial value
// should not be read by hwloc
// - flags only allows values supported by the active hwloc version
errors::call_hwloc_int_normal("hwloc_topology_export_xmlbuffer", || unsafe {
hwlocality_sys::hwloc_topology_export_xmlbuffer(
self.as_ptr(),
&mut xmlbuffer,
&mut buflen,
flags.bits(),
)
})?;
let buflen = int::expect_usize(
c_uint::try_from(buflen).expect("Got negative buffer length from hwloc"),
);
// SAFETY: - xmlbuffer does originate from self and shouldn't be aliased
// (just allocated, will not be exposed elsewhere)
// - If hwloc call succeeded, xmlbuffer and buflen should be OK
Ok(unsafe { XML::wrap(self, xmlbuffer, buflen) }
.expect("Got null pointer from hwloc_topology_export_xmlbuffer"))
}
}
bitflags! {
/// Flags to be given to [`Topology::export_xml()`]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
#[doc(alias = "hwloc_topology_export_xml_flags_e")]
pub struct XMLExportFlags: hwloc_topology_export_xml_flags_e {
/// Export XML that is loadable by hwloc v1.x
///
/// The export may miss some details about the topology.
#[doc(alias = "HWLOC_TOPOLOGY_EXPORT_XML_FLAG_V1")]
const V1 = HWLOC_TOPOLOGY_EXPORT_XML_FLAG_V1;
}
}
//
crate::impl_arbitrary_for_bitflags!(XMLExportFlags, hwloc_topology_export_xml_flags_e);
/// XML string emitted by hwloc
///
/// This behaves like a `Box<str>` and will similarly automatically
/// liberate the allocated memory when it goes out of scope.
//
// --- Implementation details
//
// # Safety
//
// As a type invariant, the data pointer is assumed to always point to a valid,
// non-aliased XML string that was allocated from specified topology.
pub struct XML<'topology> {
/// Underlying hwloc topology
topology: &'topology Topology,
/// Previously allocated XML string
/// Checked to be a valid NUL-terminated UTF-8 string
data: NonNull<[c_char]>,
}
impl<'topology> XML<'topology> {
/// Wrap an hwloc XML string
///
/// # Safety
///
/// - `base` must originate from `topology` and have no mutable aliases
/// - `len` must match `base`
///
/// # Panics
///
/// If the string is not valid UTF-8 (according to
/// <https://hwloc.readthedocs.io/en/v2.9/group__hwlocality__xmlexport.html#ga333f79975b4eeb28a3d8fad3373583ce>,
/// hwloc should only generates ASCII at the time of writing)
pub(crate) unsafe fn wrap(
topology: &'topology Topology,
base: *mut c_char,
len: usize,
) -> Option<Self> {
// Handle null pointers and invalid lengths
if base.is_null() || len == 0 {
return None;
}
// Wrap the allocation
// SAFETY: hwloc is trusted to have generated a proper, non-aliased C
// string
let s = unsafe { CStr::from_ptr(base) };
s.to_str()
.expect("Unexpected non-UTF8 XML string from hwloc");
assert_eq!(
s.to_bytes_with_nul().len(),
len,
"hwloc query emitted inconsistent results"
);
let data: *const [u8] = s.to_bytes_with_nul();
let data = (data as *const [c_char]).cast_mut();
NonNull::new(data).map(|data| Self { topology, data })
}
/// Access the inner string as a raw C string
pub const fn as_raw(&self) -> &CStr {
// SAFETY: All necesary checks are done in `wrap()`
unsafe {
let data = self.data.as_ptr() as *const [u8];
CStr::from_bytes_with_nul_unchecked(&*data)
}
}
/// Access the inner string as a Rust string
pub fn as_str(&self) -> &str {
// SAFETY: All necessary checks are done in wrap()
unsafe { std::str::from_utf8_unchecked(self.as_raw().to_bytes()) }
}
}
impl AsRef<[u8]> for XML<'_> {
fn as_ref(&self) -> &[u8] {
self.as_raw().to_bytes()
}
}
impl AsRef<CStr> for XML<'_> {
fn as_ref(&self) -> &CStr {
self.as_raw()
}
}
impl AsRef<OsStr> for XML<'_> {
fn as_ref(&self) -> &OsStr {
self.as_str().as_ref()
}
}
impl AsRef<str> for XML<'_> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for XML<'_> {
fn borrow(&self) -> &str {
self.as_ref()
}
}
impl Debug for XML<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<str as Debug>::fmt(self.as_str(), f)
}
}
impl Deref for XML<'_> {
type Target = str;
fn deref(&self) -> &str {
self.as_ref()
}
}
impl Display for XML<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<str as Display>::fmt(self.as_str(), f)
}
}
impl Drop for XML<'_> {
#[doc(alias = "hwloc_free_xmlbuffer")]
fn drop(&mut self) {
let addr = self.data.as_ptr().cast::<c_char>();
// SAFETY: - Topology is trusted to contain a valid ptr (type invariant)
// - hwloc ops are trusted not to modify *const parameters
// - addr is trusted to be valid and belong to the topology
// (type invariant)
// - addr will not be exposed to safe code after Drop
unsafe { hwlocality_sys::hwloc_free_xmlbuffer(self.topology.as_ptr(), addr) }
}
}
impl Eq for XML<'_> {}
impl Hash for XML<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl<T> Index<T> for XML<'_>
where
str: Index<T>,
{
type Output = <str as Index<T>>::Output;
fn index(&self, index: T) -> &Self::Output {
self.as_str().index(index)
}
}
impl Ord for XML<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialEq for XML<'_> {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl<T> PartialEq<T> for XML<'_>
where
for<'a> &'a str: PartialEq<T>,
{
fn eq(&self, other: &T) -> bool {
self.as_str() == *other
}
}
impl PartialOrd for XML<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
// SAFETY: No internal mutability
unsafe impl Send for XML<'_> {}
// SAFETY: No internal mutability
unsafe impl Sync for XML<'_> {}