Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

crates/dwarves-tune-graph/src/profile.rs

//! Reading what a probe saw: a Heimdall `<arch>-<libc>.probe.json` (calls
//! with the runs that made them) or an OCI `.oci.json` profile (allowed
//! names only). Read as plain JSON; no Heimdall crate is linked.

use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;

use serde_json::Value;

use crate::{Error, Seen};

/// Read a Heimdall `<arch>-<libc>.probe.json` or an OCI `.oci.json` profile.
pub fn read_seen(path: &Path) -> Result<Seen, Error> {
    let err = |msg: String| Error::Profile {
        path: path.to_path_buf(),
        msg,
    };
    let text = std::fs::read_to_string(path).map_err(|e| Error::Io {
        path: path.to_path_buf(),
        err: e,
    })?;
    let v: Value = serde_json::from_str(&text).map_err(|e| err(e.to_string()))?;
    let mut seen = Seen {
        source: path.display().to_string(),
        ..Seen::default()
    };
    if let Some(calls) = v.get("calls").and_then(Value::as_array) {
        seen.kind = "probe".into();
        seen.workload = v
            .get("workload")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_string();
        for r in v.get("runs").and_then(Value::as_array).into_iter().flatten() {
            if r.get("error_path").and_then(Value::as_bool) == Some(true) {
                if let Some(n) = r.get("name").and_then(Value::as_str) {
                    seen.error_runs.insert(n.to_string());
                }
            }
        }
        for c in calls {
            let name = c
                .get("syscall")
                .and_then(Value::as_str)
                .ok_or_else(|| err("a call without \"syscall\"".into()))?;
            let runs = seen.calls.entry(name.to_string()).or_default();
            for r in c.get("runs").and_then(Value::as_array).into_iter().flatten() {
                if let Some(r) = r.as_str() {
                    runs.insert(r.to_string());
                }
            }
        }
        for u in v.get("unknown").and_then(Value::as_array).into_iter().flatten() {
            if let Some(c) = u.get("call").and_then(Value::as_str) {
                seen.unknown.push(c.to_string());
            }
        }
        return Ok(seen);
    }
    if let Some(rules) = v.get("syscalls").and_then(Value::as_array) {
        seen.kind = "oci".into();
        let mut calls: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
        for r in rules {
            let action = r.get("action").and_then(Value::as_str).unwrap_or("");
            if action != "SCMP_ACT_ALLOW" && action != "SCMP_ACT_LOG" {
                continue;
            }
            for n in r.get("names").and_then(Value::as_array).into_iter().flatten() {
                if let Some(n) = n.as_str() {
                    calls.entry(n.to_string()).or_default();
                }
            }
        }
        seen.calls = calls;
        return Ok(seen);
    }
    Err(err(
        "neither a probe.json (\"calls\") nor an OCI profile (\"syscalls\")".into(),
    ))
}