use super::suggest::PolicySuggestion;
use std::fs::File;
use std::io::Write;
use std::path::Path;
pub fn write_yaml(s: &PolicySuggestion) -> String {
let mut out = String::new();
out.push_str("# Generated by `assay sandbox --profile`\n");
out.push_str(&format!("api_version: {}\n", s.api_version));
out.push_str("extends:\n");
for e in &s.extends {
out.push_str(&format!(" - {}\n", e));
}
out.push_str("fs:\n");
out.push_str(" allow:\n");
for p in &s.fs.allow {
out.push_str(&format!(" - \"{}\"\n", escape(p)));
}
if s.fs.deny.is_empty() {
out.push_str(" deny: []\n");
} else {
out.push_str(" deny:\n");
for p in &s.fs.deny {
out.push_str(&format!(" - \"{}\"\n", escape(p)));
}
}
out.push_str("net:\n");
if s.net.allow.is_empty() {
out.push_str(" allow: []\n");
} else {
out.push_str(" allow:\n");
for p in &s.net.allow {
out.push_str(&format!(" - \"{}\"\n", escape(p)));
}
}
out.push_str(" deny:\n");
for p in &s.net.deny {
out.push_str(&format!(" - \"{}\"\n", escape(p)));
}
out.push_str("env:\n");
out.push_str(" allow:\n");
for k in &s.env.allow {
out.push_str(&format!(" - \"{}\"\n", escape(k)));
}
out.push_str("processes:\n");
out.push_str(" allow:\n");
for x in &s.processes.allow {
out.push_str(&format!(" - \"{}\"\n", escape(x)));
}
out.push_str("meta:\n");
out.push_str(" counters:\n");
for (k, v) in &s.meta.counters {
out.push_str(&format!(" \"{}\": {}\n", escape(k), v));
}
if !s.meta.notes.is_empty() {
out.push_str(" notes:\n");
for n in &s.meta.notes {
out.push_str(&format!(" - \"{}\"\n", escape(n)));
}
}
out
}
fn escape(s: &str) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
pub fn write_json(s: &PolicySuggestion) -> anyhow::Result<String> {
Ok(serde_json::to_string_pretty(s)?)
}
pub fn save_atomic(path: &Path, content: &str) -> anyhow::Result<()> {
if path.exists() {
let meta = std::fs::symlink_metadata(path)?;
if meta.file_type().is_symlink() {
anyhow::bail!("Refusing to write to symlink: {}", path.display());
}
}
let pid = std::process::id();
let temp_path = path.with_extension(format!("tmp.{}", pid));
struct Cleanup<'a>(&'a Path);
impl Drop for Cleanup<'_> {
fn drop(&mut self) {
let _ = std::fs::remove_file(self.0);
}
}
let _cleanup = Cleanup(&temp_path);
let mut file = File::create(&temp_path)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = file.metadata()?.permissions();
perms.set_mode(0o600);
file.set_permissions(perms)?;
}
file.write_all(content.as_bytes())?;
file.sync_all()?;
drop(file);
std::fs::rename(&temp_path, path)?;
if let Some(parent) = path.parent() {
if let Ok(dir) = File::open(parent) {
let _ = dir.sync_all();
}
}
std::mem::forget(_cleanup);
Ok(())
}