Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
# libtopo

Rust bindings for illumos `libtopo`
Rust bindings for illumos
[libtopo](https://illumos.org/man/3LIB/libtopo), the hardware topology
library used by FMA (Fault Management Architecture) and other illumos
consumers.

This repo contains two crates:

- **libtopo-sys** — raw FFI bindings generated by
[bindgen](https://github.com/rust-lang/rust-bindgen)
- **libtopo** — idiomatic Rust wrapper with no raw pointers in the
public API

These crates target illumos only and link against the system `libtopo`.

## Usage

```rust
use libtopo::{Scheme, TopoHdl, WalkAction};

let hdl = TopoHdl::open()?;
let snap = hdl.snapshot()?;

snap.walk(Scheme::Hc, |node| {
let fmri = node.resource()?;
println!("{}[{}]\t{}", node.name(), node.instance(), hdl.fmri_to_string(&fmri)?);
Ok(WalkAction::Continue)
})?;
```

See [`libtopo/examples/`](libtopo/examples/) for runnable programs.

## Privileges

Most libtopo operations require elevated privileges to enumerate
hardware. Run with `pfexec` or appropriate RBAC profiles.

## Testing

```sh
pfexec cargo test
```

Tests must be run on an illumos host since they link against `libtopo`.
39 changes: 37 additions & 2 deletions libtopo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,40 @@

Idiomatic Rust bindings for illumos `libtopo`.

This create provids a safe wrapper around the raw FFI bindings in
[libtopo-sys](../libtopo-sys/).
This crate provides a safe wrapper around the raw FFI bindings in
[libtopo-sys](../libtopo-sys/), the hardware topology library used
by FMA (Fault Management Architecture) and other illumos consumers.

## Usage

```rust
use libtopo::{Scheme, TopoHdl, WalkAction};

let hdl = TopoHdl::open()?;
let snap = hdl.snapshot()?;
println!("snapshot uuid: {}", snap.uuid());

snap.walk(Scheme::Hc, |node| {
let fmri = node.resource()?;
let fmri_str = hdl.fmri_to_string(&fmri)?;
println!("{}[{}]\t{}", node.name(), node.instance(), fmri_str);
Ok(WalkAction::Continue)
})?;
```

See [`examples/`](examples/) for runnable programs.

## Privileges

Most libtopo operations require elevated privileges to enumerate
hardware. Run with `pfexec` or appropriate RBAC profiles.

## Testing

Tests must be run on an illumos host since they link against `libtopo`.
Walker-dependent integration tests gracefully skip when the host has no
hardware topology (e.g. a CI VM).

```sh
pfexec cargo test
```
50 changes: 50 additions & 0 deletions libtopo/examples/fmri_resolve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! Parse an FMRI string and report its status (present/replaced/unusable).
//!
//! Run with `pfexec`:
//!
//! ```sh
//! pfexec cargo run --example fmri_resolve -- 'hc:///chassis=0'
//! ```

use libtopo::TopoHdl;

fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!(
"usage: {} <fmri-string>",
args.first().map_or("fmri_resolve", |s| s.as_str())
);
std::process::exit(2);
}
let fmri_str = &args[1];

let hdl = TopoHdl::open().expect("failed to open libtopo handle");
// Hold a snapshot — fmri_present/replaced/unusable consult the
// topology to resolve the resource.
let _snap = hdl.snapshot().expect("failed to take snapshot");

let fmri = match hdl.fmri_parse(fmri_str) {
Ok(f) => f,
Err(e) => {
eprintln!("fmri_parse failed: {e}");
std::process::exit(1);
}
};

let formatted = hdl
.fmri_to_string(&fmri)
.unwrap_or_else(|e| format!("<fmri_to_string failed: {e}>"));
println!("fmri: {formatted}");

print_predicate("present", hdl.fmri_present(&fmri));
print_predicate("replaced", hdl.fmri_replaced(&fmri));
print_predicate("unusable", hdl.fmri_unusable(&fmri));
}

fn print_predicate(name: &str, result: Result<bool, libtopo::Error>) {
match result {
Ok(b) => println!("{name}: {b}"),
Err(e) => println!("{name}: error: {e}"),
}
}
147 changes: 147 additions & 0 deletions libtopo/examples/list_topology.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//! Open a libtopo handle, take a snapshot, and walk a topology scheme,
//! printing each node's name, instance, FMRI, and label (if any).
//!
//! The scheme defaults to `hc`; pass another scheme name as the first
//! positional argument to walk it instead. Pass `-v` / `--verbose`
//! (anywhere in argv) to also dump every property group and property:
//!
//! ```sh
//! pfexec cargo run --example list_topology # hc (default)
//! pfexec cargo run --example list_topology -- cpu
//! pfexec cargo run --example list_topology -- pcie -v
//! pfexec cargo run --example list_topology -- -v
//! ```

use libtopo::{PropValue, Scheme, TopoHdl, WalkAction};

fn main() {
let args: Vec<String> = std::env::args().collect();
let verbose = args.iter().any(|a| a == "-v" || a == "--verbose");
let scheme = match args
.iter()
.skip(1)
.find(|a| !a.starts_with('-'))
.map(String::as_str)
{
None => Scheme::Hc,
Some(name) => match parse_scheme(name) {
Some(s) => s,
None => {
eprintln!("unknown scheme: {name:?}");
eprintln!(
"valid schemes: hc, cpu, mem, dev, mod, svc, sw, zfs, pcie, path, fmd, pkg, legacy-hc"
);
std::process::exit(2);
}
},
};

let hdl = TopoHdl::open().expect("failed to open libtopo handle");
let snap = hdl.snapshot().expect("failed to take snapshot");

println!("snapshot uuid: {}", snap.uuid());
println!("scheme: {:?}", scheme);
println!();

let walk = snap.walk(scheme, |node| {
let fmri = match node.resource() {
Ok(f) => f,
Err(e) => {
eprintln!(
"warn: resource lookup failed on node {}[{}]: {e}",
node.name(),
node.instance(),
);
return Ok(WalkAction::Continue);
}
};
let fmri_str = hdl
.fmri_to_string(&fmri)
.unwrap_or_else(|e| format!("<fmri_to_string failed: {e}>"));
println!("{}[{}]\t{}", node.name(), node.instance(), fmri_str);
if let Ok(label) = node.label()
&& !label.is_empty()
{
println!(" label: {label}");
}

if verbose {
match node.property_groups() {
Ok(groups) => {
for pg in &groups {
println!(" [{}]", pg.name);
for p in &pg.properties {
print_prop_value(&hdl, &p.name, &p.value);
}
}
}
Err(e) => eprintln!(
"warn: property_groups failed on {}[{}]: {e}",
node.name(),
node.instance(),
),
}
}
Ok(WalkAction::Continue)
});

if let Err(e) = walk {
eprintln!("walk failed: {e}");
std::process::exit(1);
}
}

fn print_prop_value(hdl: &TopoHdl, name: &str, v: &PropValue) {
match v {
PropValue::String(s) => println!(" {name} = {s:?}"),
PropValue::Boolean(v) => println!(" {name} = {v}"),
PropValue::Int32(v) => println!(" {name} = {v}"),
PropValue::UInt32(v) => println!(" {name} = {v}"),
PropValue::Int64(v) => println!(" {name} = {v}"),
PropValue::UInt64(v) => println!(" {name} = {v}"),
PropValue::Double(v) => println!(" {name} = {v}"),
PropValue::Fmri(f) => match hdl.fmri_to_string(f) {
Ok(s) => println!(" {name} = {s}"),
Err(_) => println!(" {name} = <fmri (unstringable)>"),
},
PropValue::Time(t) => println!(" {name} = {t} (time)"),
PropValue::Size(b) => println!(" {name} = {b} bytes"),
PropValue::StringArray(vs) => println!(" {name} = {vs:?}"),
PropValue::Int32Array(vs) => println!(" {name} = {vs:?}"),
PropValue::UInt32Array(vs) => println!(" {name} = {vs:?}"),
PropValue::Int64Array(vs) => println!(" {name} = {vs:?}"),
PropValue::UInt64Array(vs) => println!(" {name} = {vs:?}"),
PropValue::FmriArray(fs) => {
let rendered: Vec<String> = fs
.iter()
.map(|f| {
hdl.fmri_to_string(f)
.unwrap_or_else(|_| "<unstringable>".into())
})
.collect();
println!(" {name} = {rendered:?}");
}
PropValue::Unknown { type_code } => {
println!(" {name} = <unsupported type {type_code}>")
}
}
}

fn parse_scheme(s: &str) -> Option<Scheme> {
match s {
"hc" => Some(Scheme::Hc),
"cpu" => Some(Scheme::Cpu),
"mem" => Some(Scheme::Mem),
"dev" => Some(Scheme::Dev),
"mod" => Some(Scheme::Mod),
"svc" => Some(Scheme::Svc),
"sw" => Some(Scheme::Sw),
"zfs" => Some(Scheme::Zfs),
"pcie" => Some(Scheme::Pcie),
"path" => Some(Scheme::Path),
"fmd" => Some(Scheme::Fmd),
"pkg" => Some(Scheme::Pkg),
"legacy-hc" => Some(Scheme::Legacy),
_ => None,
}
}