//! `brokerd serve` fails closed at startup with a runbook pointer for every reason it cannot start, //! and never panics on its own command line. Found in the M3a review (findings 3 and 7). #[path = "support/tmp.rs"] mod tmp; use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::process::{Command, Output}; use tmp::TempDir; const START_FAILED: &str = "see docs/runbook.md#brokerd-start-failed"; fn brokerd, S: AsRef>(args: I) -> Output { Command::new(env!("CARGO_BIN_EXE_brokerd")) .args(args) .output() .unwrap() } fn stderr(out: &Output) -> String { String::from_utf8_lossy(&out.stderr).into_owned() } /// A config whose home is `dir`, with `sockets` as the `[sockets]` table's body. fn config(dir: &TempDir, sockets: &str) -> std::path::PathBuf { let home = dir.path().join("home"); let grants = dir.path().join("grants"); std::fs::create_dir_all(&grants).unwrap(); std::fs::create_dir_all(&home).unwrap(); dir.write( "brokerd.toml", &format!( "[paths]\nhome = \"{}\"\ngrants = \"{}\"\n[sockets]\n{sockets}\n", home.display(), grants.display() ), ) } fn fails_with_pointer(out: &Output) { assert_eq!(out.status.code(), Some(1), "{}", stderr(out)); assert!( stderr(out).trim_end().ends_with(START_FAILED), "{:?} must end with {START_FAILED:?}", stderr(out) ); } #[test] fn a_missing_config_names_the_entry() { let dir = TempDir::new("ptr-missing"); let out = brokerd([ OsStr::new("serve"), OsStr::new("--config"), dir.path().join("nope.toml").as_os_str(), ]); fails_with_pointer(&out); } #[test] fn an_invalid_config_names_the_entry() { let dir = TempDir::new("ptr-invalid"); let path = dir.write("brokerd.toml", "[paths]\nhome = 3\n"); let out = brokerd([ OsStr::new("serve"), OsStr::new("--config"), path.as_os_str(), ]); fails_with_pointer(&out); } #[test] fn a_socket_that_cannot_be_bound_names_the_entry() { let dir = TempDir::new("ptr-bind"); let long = dir.path().join("s".repeat(120)).join("broker.sock"); let path = config(&dir, &format!("broker = \"{}\"", long.display())); fails_with_pointer(&brokerd([ OsStr::new("serve"), OsStr::new("--config"), path.as_os_str(), ])); } #[test] fn a_socket_directory_that_cannot_be_made_names_the_entry() { let dir = TempDir::new("ptr-dir"); let file = dir.write("a-file", ""); let path = config( &dir, &format!("broker = \"{}\"", file.join("run/broker.sock").display()), ); fails_with_pointer(&brokerd([ OsStr::new("serve"), OsStr::new("--config"), path.as_os_str(), ])); } /// The socket's directory is made private (0700). Through a symbolic link that would change the /// directory it points to, so a link is refused, and the target keeps its mode. #[test] fn a_socket_directory_that_is_a_symbolic_link_is_refused() { use std::os::unix::fs::PermissionsExt; let dir = TempDir::new("ptr-link"); let shared = dir.path().join("shared"); std::fs::create_dir(&shared).unwrap(); std::fs::set_permissions(&shared, std::fs::Permissions::from_mode(0o755)).unwrap(); let link = dir.path().join("link"); std::os::unix::fs::symlink(&shared, &link).unwrap(); let path = config( &dir, &format!("broker = \"{}\"", link.join("broker.sock").display()), ); let out = brokerd([ OsStr::new("serve"), OsStr::new("--config"), path.as_os_str(), ]); fails_with_pointer(&out); assert!(stderr(&out).contains("symbolic link"), "{}", stderr(&out)); let mode = std::fs::metadata(&shared).unwrap().permissions().mode() & 0o777; assert_eq!(mode, 0o755, "the link's target keeps its mode"); } /// A socket directly in `/` would make `/` private: refused before anything is changed. #[test] fn a_socket_in_the_root_directory_is_refused() { let dir = TempDir::new("ptr-root"); let path = config(&dir, "broker = \"/broker.sock\""); let out = brokerd([ OsStr::new("serve"), OsStr::new("--config"), path.as_os_str(), ]); fails_with_pointer(&out); assert!( stderr(&out).contains("directory of its own"), "{}", stderr(&out) ); } /// A config path that is not UTF-8 is still a path: it is read (here: not found), not a panic. #[test] fn a_config_path_that_is_not_utf8_is_read_as_a_path() { let dir = TempDir::new("ptr-os"); let name = dir.path().join(OsStr::from_bytes(b"conf-\xff.toml")); let out = brokerd([ OsStr::new("serve"), OsStr::new("--config"), name.as_os_str(), ]); fails_with_pointer(&out); } /// A flag that is not UTF-8 is not a flag brokerd knows: usage, exit 2, no panic. #[test] fn a_flag_that_is_not_utf8_is_a_usage_error() { let out = brokerd([OsStr::new("serve"), OsStr::from_bytes(b"--\xff")]); assert_eq!(out.status.code(), Some(2), "{}", stderr(&out)); assert!(stderr(&out).starts_with("usage: brokerd serve")); let out = brokerd([OsStr::from_bytes(b"\xff")]); assert_eq!(out.status.code(), Some(2), "{}", stderr(&out)); } #[test] fn the_entries_exist() { let runbook = std::fs::read_to_string( Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/runbook.md"), ) .unwrap(); for entry in ["## brokerd-start-failed", "## brokerd-listener-lost"] { assert!(runbook.lines().any(|l| l == entry), "{entry}"); } }