Skip to content

Commit 76e4b4f

Browse files
committed
sandlock-oci: write fatal errors to runc --log so containerd surfaces them
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent d4c3ce9 commit 76e4b4f

1 file changed

Lines changed: 53 additions & 1 deletion

File tree

crates/sandlock-oci/src/main.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,38 @@ enum Command {
213213
},
214214
}
215215

216-
fn main() -> Result<()> {
216+
/// Append a fatal error to the runc-compatible `--log` file so containerd can
217+
/// surface the real failure reason. Never panics: if the file cannot be opened
218+
/// the error is dropped here (it is still printed to stderr by the caller).
219+
fn write_error_log(path: &std::path::Path, format: LogFormat, err: &anyhow::Error) {
220+
use std::io::Write;
221+
// `{:#}` renders the full anyhow context chain on one line.
222+
let msg = format!("{:#}", err);
223+
let line = match format {
224+
// serde_json handles correct escaping of arbitrary message text.
225+
LogFormat::Json => serde_json::json!({ "level": "error", "msg": msg }).to_string(),
226+
LogFormat::Text => msg,
227+
};
228+
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
229+
let _ = writeln!(f, "{}", line);
230+
}
231+
}
232+
233+
fn main() {
217234
let cli = Cli::parse();
235+
// Capture the log destination before `cli.command` is consumed by `run`.
236+
let log = cli.log.clone();
237+
let log_format = cli.log_format;
238+
if let Err(err) = run(cli) {
239+
if let Some(path) = log.as_deref() {
240+
write_error_log(path, log_format, &err);
241+
}
242+
eprintln!("Error: {:#}", err);
243+
std::process::exit(1);
244+
}
245+
}
218246

247+
fn run(cli: Cli) -> Result<()> {
219248
// Resolve the state-dir root once, before any state I/O or fork, so the
220249
// supervisor child inherits the same location.
221250
state::init_state_dir(cli.root.as_deref().and_then(|p| p.to_str()));
@@ -955,6 +984,29 @@ mod tests {
955984
assert_eq!(explicit.rootless, Some(false));
956985
}
957986

987+
#[test]
988+
fn error_log_json_is_parseable_with_msg() {
989+
let dir = tempfile::tempdir().unwrap();
990+
let path = dir.path().join("log.json");
991+
let err = anyhow::anyhow!("boom").context("creating sandbox");
992+
write_error_log(&path, LogFormat::Json, &err);
993+
let content = std::fs::read_to_string(&path).unwrap();
994+
let v: serde_json::Value = serde_json::from_str(content.trim()).unwrap();
995+
assert_eq!(v["level"], "error");
996+
let msg = v["msg"].as_str().unwrap();
997+
assert!(msg.contains("creating sandbox"), "msg was: {msg}");
998+
assert!(msg.contains("boom"), "msg was: {msg}");
999+
}
1000+
1001+
#[test]
1002+
fn error_log_text_is_plain_message() {
1003+
let dir = tempfile::tempdir().unwrap();
1004+
let path = dir.path().join("log.txt");
1005+
write_error_log(&path, LogFormat::Text, &anyhow::anyhow!("plain failure"));
1006+
let content = std::fs::read_to_string(&path).unwrap();
1007+
assert_eq!(content.trim(), "plain failure");
1008+
}
1009+
9581010
#[test]
9591011
fn parse_signal_numeric() {
9601012
assert_eq!(parse_signal("15").unwrap(), libc::SIGTERM);

0 commit comments

Comments
 (0)