use std::io::Write;
use harn_hostlib::sandbox::conformance::{run_conformance_with_progress, ConformanceReport};
use harn_vm::process_sandbox::conformance::Verdict;
use harn_vm::process_sandbox::enforcement::MechanismLimit;
use serde::Serialize;
use crate::json_envelope::{to_string_pretty, JsonEnvelope};
pub(crate) const DOCTOR_SANDBOX_SCHEMA_VERSION: u32 = 1;
#[derive(Serialize)]
struct DoctorSandboxReport {
#[serde(flatten)]
report: ConformanceReport,
mechanism_limits: Vec<MechanismLimit>,
}
pub(crate) fn run(json: bool) -> i32 {
let report = run_conformance_with_progress(|progress| {
eprintln!("{}", progress.receipt_line());
let _ = std::io::stderr().flush();
});
let report = DoctorSandboxReport {
mechanism_limits: harn_vm::process_sandbox::active_backend_mechanism_limits(),
report,
};
let holds = report.report.holds();
if json {
println!("{}", to_string_pretty(&envelope(&report)));
} else {
print!("{}", render(&report));
}
i32::from(!holds)
}
fn envelope(report: &DoctorSandboxReport) -> JsonEnvelope<&DoctorSandboxReport> {
if report.report.holds() {
return JsonEnvelope::ok(DOCTOR_SANDBOX_SCHEMA_VERSION, report);
}
let mut envelope = JsonEnvelope::err(
DOCTOR_SANDBOX_SCHEMA_VERSION,
"sandbox_conformance_failed",
"Sandbox confinement did not hold for every applicable case.",
);
envelope.data = Some(report);
envelope
}
fn render(doctor: &DoctorSandboxReport) -> String {
let report = &doctor.report;
let mut out = format!(
"Process sandbox: backend={} mechanism={} enforcing={}\n",
report.backend, report.filesystem_mechanism, report.enforcing
);
for limit in &doctor.mechanism_limits {
out.push_str(&format!(
" limit: {} ({})\n",
limit.as_str(),
limit.description()
));
}
out.push('\n');
for case in &report.cases {
let (status, note) = match &case.verdict {
Verdict::Conforms => ("ok", String::new()),
Verdict::Escaped { target } => ("ESCAPED", target.clone()),
Verdict::Overrefused { target, observed } => {
("OVERREFUSED", format!("{target} ({observed:?})"))
}
Verdict::Contradiction { reason, .. } => ("CONTRADICTION", reason.clone()),
Verdict::ProbeBroken { reason } => ("PROBE BROKEN", reason.clone()),
Verdict::NotMeasured { reason } => ("not measured", reason.clone()),
Verdict::NotEnforced { target } => ("not enforced", target.clone()),
Verdict::NotApplicable { reason } => ("n/a", reason.clone()),
};
out.push_str(&format!(" {status:<14} {}", case.case));
if !note.is_empty() {
out.push_str(&format!(" {note}"));
}
out.push('\n');
}
let failed = report.failing().len();
let not_measured = report.not_measured().len();
let not_enforced = report.not_enforced().len();
out.push_str(&format!(
"\n{} cases: {} hold, {failed} failed, {not_measured} not measured, \
{not_enforced} not enforced\n",
report.cases.len(),
report.conforming(),
));
if not_enforced > 0 {
out.push_str(
"This backend does not confine the not-enforced cases, and says so; do not rely on \
it for them.\n",
);
}
if failed == 0 && not_measured > 0 {
out.push_str(
"Confinement is not enforced on this host for the unmeasured cases; do not rely on it.\n",
);
}
out
}